在Nodejs的开发过程中,异步这个话题是无论如何都躲不过去的,关于异步的文章已经有过许多篇了,我也不打算写在开发Web应用的过程中,该如何在Nodejs中处理异步代码。在前些日子,我跟单元测试覆盖率这个指标杠上了,因为自己在写一个Nodejs的工程,我希望这个工程的测试代码量不要太少,目标是100%的行覆盖率,所以最近写了许多的单元测试代码。使用的测试框架是Mocha,断言库是Chai,那么今天我们就来聊聊在单元测试中,处理异步代码的各种姿势。
处理promise
const { query } = require('../app/utils/async-db');
const { should } = require('chai');
const mysql = require('mysql');
should();
/**
* 测试数据库连接的正确状态
*/
describe('mysql connect success state', function() {
it('should return an array', function(done) {
let sql = 'SELECT * FROM `Users`';
query(sql)
.then((rows) => {
rows.should.be.an('array');
done();
})
.catch(err => {
done(err);
throw err;
});
});
});
先来看看今天的例子,这段代码就是测试数据库连接状态的库,在断言库中我偏向于使用should类型的,因为更加的语义化,更符合TDD的阅读习惯。而这段代码看似没有问题,但是运行起来会报错:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
为什么呢,原因是在第二行、第四行。
const { should } = require('chai');
...
should();
在这样引用了should之后,是无法像刚才代码中那样使用should的,为什么我会写出这样的语法呢?我承认我当时偷懒随便看了篇博客就照猫画虎了,以后一定要跟着官方文档来!!!所以我们这里先纠正错误,正确的代码如下:
const { query } = require('../app/utils/async-db');
const should = require('chai').should();
const mysql = require('mysql');
/**
* 测试数据库连接的正确状态
*/
describe('mysql connect success state', function() {
it('should return an array did not have done', function(done) {
let sql = 'SELECT * FROM `Users`';
query(sql)
.then((rows) => {
rows.should.be.an('array');
done();
})
.catch(err => {
done(err);
// throw err;
});
});
});
这样,在promise中,在then里直接写断言,之后再跟上done,表示测试完成,就可以成功的完成异步测试,这种方式是done回调的方式。
而还有直接返回promise的方式,写法如下:
/**
* 测试数据库连接的正确状态
*/
describe('mysql connect success state', function() {
it('should return an array did not have done', function() {
let sql = 'SELECT * FROM `Users`';
return query(sql)
.then((rows) => {
rows.should.be.an('array');
});
});
});
直接说一下写法的区别吧,在第二行代码的it块内,回调的function中不要再加入done回调的,不然测试程序会一直等待你的done回调,当超时之后就会报错了。而去除done回调之后,直接写返回结果就好了,如果catch到了error,那么直接会被抛出,测试失败。
这两种方法写完,应该还有很多同学觉得这样写非常啰嗦吧,那么我们来看一个chai断言库的中间件,这个中间件可以大大简化promise相关的断言,这个库就是chai-as-promised
。这个库中提供了一个最重要的Api就是should.eventually
,直接按字面意思去理解这个链式api吧,意味着它会等待promise的最终执行结果,来测试断言。还是刚才的例子,用这个小插件改写完之后是这样的。
const { query } = require('../app/utils/async-db');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
/**
* chai-as-promised库的简单使用
*/
describe('Mysql connect', function() {
// 记得使用chai-as-promised的时候 这里的function不要加 done 参数
it('should return an array', function() {
let sql = 'SELECT * FROM `Users`';
return query(sql).should.eventually.be.an('array');
});
});
瞬间测试的代码块内只剩下两行代码了,是不是看着分外的爽?稍微学习一下这样的用法,相信异步的单元测试,从此以后对同学们来说就是小菜一碟咯。