参考了 egg-RESTfulAPI 写的RESTfulAPI 样例,里面没有单元测试代码学习。 然后自己写单元测试代码,写到 service层的 ctx.throw 就卡住了。 ctx.throw 到底有没有抛出异常
用assert.doesNotThrow 来断言是可以通过测试
npm test
用assert.throws 来断言是不可以通过测试,但是我一直怀疑是第二个参数的对象写得不对
npm test
直接运行是不能到下一个断言,感觉是给抛出异常了
npm test
对doesNotThrow 测试过,是第一个参数block 内没有抛出异常是才会通过断言,那直接运行的时候抛出来的是什么? 求大大指导一下,最好提供一下你们在面对这种单元测试的时候是怎么写的。
可以用 https://github.com/node-modules/assert-extends
// test/app/service/user.test.js
const assert = require('assert-extends');
describe('test/app/service/user.test.js', () => {
it('should throw', async () => {
const ctx = app.mockContext();
// 这里要 return 或者 await 下
return assert.asyncThrows(async () => {
// 测试代码
await ctx.service.user.echo();
}, /this is an error/);
// 最后是需要正则式,不要用字符串,字符串的用法是错误的,在 10 里面会抛错。
});
});
对应的 Service,其实可以直接 throw
不用 ctx.throw
// app/service/user.js
module.exports = class UserService extends Service {
async echo() {
await sleep(100);
// throw by ctx
this.ctx.throw(new Error('this is an error'));
// could just throw it
// throw new Error('this is an error');
}
};
你截图中的报错跟 assert throw 应该关系不大,而是你的 user 没挂载成功,上面看你有一句 factory 很奇怪。 最好提供下最小可复现仓库看看。
根据我上面说的自己排查下吧。
刚才提到了,应该不是测试这块的问题,你先查下为啥 user 是 undefined 吧。
另外,提供可复现仓库的意思不是截图。。。
感谢 @atian25 查了一下egg.js的 assert 是用 power-assert 的,目测是没有 assert-extends 内的 asyncThrows 方法,换成您的这个方式基本解决问题,power-assert 的 throws 不兼容 async 的方法,所以是捕获不到那个异常。
然后目前遇到一个新的情况。可以麻烦您帮忙看看吗?
async update(id, payload) {
const { ctx } = this;
const { User } = ctx.model;
const user = await User.findById(id);
let canSave = true;
if (!user) {
canSave = false;
ctx.throw(404, ctx.__('Data not found'));
}
// 修改电话时,检查是否有重复号码
if (payload.mobiePhone) {
if (!await this.canUseMobiePhone({ mobiePhone: payload.mobiePhone, neId: id })) {
canSave = false;
ctx.throw(422, ctx.__('This phone number {0} already exists', [ payload.mobiePhone ]));
}
}
if (canSave) {
const keyArr = [ 'mobiePhone', 'email', 'nickname', 'lastAt', 'isLock', 'lastLanguage', 'headimgurl', 'genderType' ];
const willUpdate = ctx.helper.getKeyObj({ obj: payload, keyArr });
await user.update(willUpdate);
return user;
}
}
it('should not update succeed repeat mobiePhone', async () => {
const user1 = await app.factory.create(baseModel);
const user2 = await app.factory.create(baseModel);
const willUpdate = {
mobiePhone: user2.mobiePhone,
};
// await app.mockContext().service.user.update(user1.id, willUpdate);
assert.asyncThrows(
async () => {
await app.mockContext().service.user.update(user1.id, willUpdate);
},
/^UnprocessableEntityError: This phone number 86_[0-9]* already exists$/
);
});
我现在想对这个update方式内的422 那个异常做单元测试,在没有捕捉之前提示的异常情况是这样的
这里我也按照了您建议的正则的方式来写,但是还是不行。
更奇怪的是,同样的代码我在另外一个create 方式内也有,但是哪里就测试通过。我更是多次复制,过去还是一样的不能正确的断言到那个异常。这是为什么呢?
return assert.asyncThrows()
or await assert.asyncThrows()
assert.asyncThrows()
返回的是一个 Promise,如果你没 await 或 return 的话,mocha 的测试根本来不及等待它。
@atian25 哈哈哈原来是要这样! 谢谢
@atian25 弱弱地问一下
middleware 层的类似这样的 方法该怎么写单元测试? mock 可以模拟app运行异常吗?对应的测试思路应该是怎样?
module.exports = () => {
return async function errorHandler(ctx, next) {
try {
await next();
} catch (err) {
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
ctx.app.emit('error', err, ctx);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
const error = status === 500 && ctx.app.config.env === 'prod'
? ctx.__('Internal Server Error')
: err.message;
// 从 error 对象上读出各个属性,设置到响应中
ctx.body = { error };
if (status === 422) {
ctx.body.detail = err.errors;
}
ctx.status = status;
}
};
};
你可以在 test 的 before 里面手动注册一个路由
describe('test/app/controller/home.test.js', () => {
before(() => {
app.get('/test_fail', async ctx => {
ctx.throw(new Error('this is a test error'));
});
});
it('should GET /test_fail', () => {
return app.httpRequest()
.get('/test_fail')
.expect(/Error: this is a test error/)
.expect(500);
});
});