EGG单元测试

版权声明: https://blog.csdn.net/weixin_33768153/article/details/84833174

单元测试的优点

  • 代码质量持续有保障
  • 重构正确性保障
  • 增强自信心
  • 自动化运行

测试框架

测试约定

测试目录结构

  • 我们约定 test 目录为存放所有测试脚本的目录,测试所使用到的 fixtures 和相关辅助脚本都应该放在此目录下。

  • 测试文件的目录和我们需要测试的文件目录必须保持一直

  • 测试脚本文件统一按 ${filename}.test.js 命名,必须以 .test.js 作为文件后缀。 一个应用的测试目录示例:

    test
    ├── controller
    │   └── home.test.js
    └── service
      └── user.test.js
    

测试运行工具

统一使用 egg-bin 来运行测试脚本, 自动将内置的 Mocha、co-mocha、power-assert,nyc 等模块组合引入到测试脚本中, 让我们聚焦精力在编写测试代码上,而不是纠结选择那些测试周边工具和模块。

  "scripts": {
    "test": "egg-bin test",
    "cov": "egg-bin cov"
  }

mock

正常来说,如果要完整手写一个 app 创建和启动代码,还是需要写一段初始化脚本的, 并且还需要在测试跑完之后做一些清理工作,如删除临时文件,销毁 app。

常常还有模拟各种网络异常,服务访问异常等特殊情况。

所以我们单独为框架抽取了一个测试 mock 辅助模块:egg-mock, 有了它我们就可以非常快速地编写一个 app 的单元测试,并且还能快速创建一个 ctx 来测试它的属性、方法和 Service 等。

app

在测试运行之前,我们首先要创建应用的一个 app 实例, 通过它来访问需要被测试的 Controller、Middleware、Service 等应用层代码。

// test/controller/home.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/home.test.js', () => {
});

ctx

const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/news.test.js', () => {
  it('should get a ctx', () => {
    const ctx=app.mockContext({
          session: {
            user:{name:'leo'}
        }
    });
    assert(ctx.method === 'GET');
    assert(ctx.url==='/');
    assert(ctx.session.user.name == 'leo');
  });
});

测试执行顺序

特别需要注意的是执行顺序,尽量保证在执行某个用例的时候执行相关代码。

describe('egg test', () => {
  before(() => console.log('order 1'));
  before(() => console.log('order 2'));
  after(() => console.log('order 6'));
  beforeEach(() => console.log('order 3'));
  afterEach(() => console.log('order 5'));
  it('should worker', () => console.log('order 4'));
});

异步测试

egg-bin 支持测试异步调用,它支持多种写法:

// 使用返回 Promise 的方式
it('should redirect', () => {
  return app.httpRequest()
    .get('/')
    .expect(302);
});

// 使用 callback 的方式
it('should redirect', done => {
  app.httpRequest()
    .get('/')
    .expect(302, done);
});

// 使用 async
it('should redirect', async () => {
  await app.httpRequest()
    .get('/')
    .expect(302);
});

Controller 测试

app.httpRequest()egg-mock 封装的 SuperTest 请求实例。

const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/home.test.js', () => {
  it('homeController', async () => {
        await app.httpRequest()
            .get('/')
            .expect(200)
            .expect('hello');

            let result = await app.httpRequest()
            .get('/')
            .expect(200)
                .expect('hello');
           assert(result.status == 200);
  });
});

post

    router.post('/post',controller.home.post);
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/controller/home.test.js', () => {
    it('homeController',async () => {
        let user={name: 'leo'};
        app.mockCsrf();
        await app.httpRequest()
            .post('/post')
            .type('form')
            .send(user)
            .expect(200)
            .expect(user);
  });
});

service

Service 相对于 Controller 来说,测试起来会更加简单, 我们只需要先创建一个 ctx,然后通过 ctx.service.${serviceName} 拿到 Service 实例, 然后调用 Service 方法即可。

const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/service/news.test.js', () => {
    it('newsService',async () => {
        let ctx = app.mockContext();
        let result=await ctx.service.news.list(1,5);
        assert(result.length == 5);
    });
});

Extend 测试

应用可以对 Application、Request、Response、Context 和 Helper 进行扩展。 我们可以对扩展的方法或者属性针对性的编写单元测试。

application

egg-mock 创建 app 的时候,已经将 Application 的扩展自动加载到 app 实例了, 直接使用这个 app 实例访问扩展的属性和方法即可进行测试。

app/extend/application.js

let cacheData={};
exports.cache={
    get(key) {
        return cacheData[key];
    },
    set(key,val) {
        cacheData[key]=val;
    }
}

test/app/extend/cache.test.js

const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/cache.test.js', () => {
    it('cache',async () => {
        app.cache.set('name','leo');
        assert(app.cache.get('name') == 'leo');
  });
});
context

Context 测试只比 Application 多了一个 app.mockContext() 步骤来模拟创建一个 Context 对象。 app/extend/context.js

exports.language=function () {
    return this.get('accept-language');
}

test/app/extend/context.test.js

const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/context.test.js',() => {
    let language="zh-cn";
    it('cache',async () => {
        const ctx=app.mockContext({
            headers: {
                'Accept-Language':language
            }
        });
        //console.log('ctx.lan',ctx.lan())
        assert(ctx.language() == language);
  });
});
Request

通过 ctx.request 来访问 Request 扩展的属性和方法,直接即可进行测试。

module.exports={
    get isChrome() {
        const userAgent=this.get('User-Agent').toLowerCase();
        return userAgent.includes('chrome');
    }
}
const { app, mock, assert } = require('egg-mock/bootstrap');
describe('test/app/extend/request.test.js',() => {
    it('cache',async () => {
        const ctx=app.mockContext({
            headers: {
                'User-Agent':'I love Chrome'
            }
        });
        assert(ctx.request.isChrome);
  });
});
response

Response 测试与 Request 完全一致。 通过 ctx.response 来访问 Response 扩展的属性和方法,直接即可进行测试。

module.exports = {
  get isSuccess() {
    return this.status === 200;
  },
};
describe('isSuccess()', () => {
  it('should true', () => {
    const ctx = app.mockContext();
    ctx.status = 200;
    assert(ctx.response.isSuccess === true);
  });

  it('should false', () => {
    const ctx = app.mockContext();
    ctx.status = 404;
    assert(ctx.response.isSuccess === false);
  });
});
### Helper

Helper 测试方式与 Service 类似,也是通过 ctx 来访问到 Helper,然后调用 Helper 方法测试。

module.exports = {
  money(val) {
    const lang = this.ctx.get('accept-language');
    if (lang.includes('zh-cn')) {
      return `¥ ${val}`;
    }
    return `$ ${val}`;
  },
};
describe('money()', () => {
  it('should RMB', () => {
    const ctx = app.mockContext({
      // 模拟 ctx 的 headers
      headers: {
        'Accept-Language': 'zh-CN,zh;q=0.5',
      },
    });
    assert(ctx.helper.money(100) === '¥ 100');
  });

  it('should US Dolar', () => {
    const ctx = app.mockContext();
    assert(ctx.helper.money(100) === '$ 100');
  });
});

猜你喜欢

转载自blog.csdn.net/weixin_33768153/article/details/84833174