1. 简介
Mocha是一个能够运行在Node和浏览器中的多功能的JavaScript测试框架,它让异步测试简单且有趣。Mocha连续地运行测试,并给出灵活而精确的报告,同时能够将错误精确地映射到测试用例上。它托管在GitHub上。
2. 开始使用
2.1 安装
npm 全局安装:
npm install --global mocha
或者作为开发依赖安装在项目中:
npm install mocha --save-dev
安装Mocha v3.0.0或者更新的版本,你需要v1.4.0或者更新版本的npm。此外,运行Mocha的Node版本不能低于v0.10
Mocha也能通过Bower安装,还可通过cdnjs进行引用。
2.2 起步
npm install mocha
mkdir test
$EDITOR test/test.js # 或者使用你喜欢的编辑器打开
在编辑器中:
var assert = require('assert')
describe('Array', function () {describe('#indexOf()', function() {it('未找到值时应当返回-1', function () {assert.equal(-1, [1, 2, 3].indexOf(4))})})
})
回到命令行:
mocha test.js
//输出如下
Array#indexOf()√ 未找到值时应当返回-11 passing (9ms)
上面这段代码,就是测试脚本,它可以独立执行。测试脚本里面应该包括一个或多个describe
块,每个describe
块应该包括一个或多个it
块。
describe
块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称(“Array”),第二个参数是一个实际执行的函数。
it
块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("#indexOf()"),第二个参数是一个实际执行的函数。
2.3 断言
Mocha允许你使用你喜欢的断言库。在之后的例子中,我们使用了Node中内置的断言模块——但通常情况下,只要它能抛出异常就行。这意味着你可以使用下列断言库:
- should.js - BDD风格贯穿始终
- expect.js - expect() 风格的断言
- chai - expect()、assert()和 should风格的断言‘
- better-assert - C风格的自文档化的assert()
- unexpected - “可扩展的BDD断言工具”
2.4 异步代码
用Mocha测试异步代码简单的不要不要的!测试运行完了调用一下回调函数就行。只需要在it()
中添加一个回调,Mocha就知道应该等到这个回调被调用时才结束这个测试用例的运行。
describe('User', function() {describe('#save()', function() {it('should save without error', function(done) {var user = new User('Luna');user.save(function(err) {if (err) done(err);else done();});});});
});
简便起见,done()
函数接受一个error参数,所以上面的代码可以这么写:
describe('User', function() {describe('#save()', function() {it('should save without error', function(done) {var user = new User('Luna');user.save(done);});});
});
Promise
有时,与其使用done()
回调函数,你会想在你的异步代码中返回一个Promise,当你正在测试的API是返回一个Promise而不是使用回调时这会很有帮助:
beforeEach(function () {return db.clear().then(function () {return db.save([tobi, loki, jane])})
})describe('#find()', function () {it('返回匹配的记录', function () {return db.find({ type: 'User' }).should.eventually.have.length(3)})
})
接下来的例子将会使用chai-as-promised来获得流畅的promise断言
在Mocha 3.0及更新的版本中,同时返回一个Promise和调用done()
会导致一个异常,下面的代码是错误的:
const assert = require('assert')it('应该结束这个测试用例', function (done) {return new Promise(function (resolve) {assert.ok(true)resolve()}).then(done)
})
上面的测试会报错:Error: Resolution method is overspecified. Specify a callback *or* return a Promise; not both.
。在v3.0.0以下的版本,done()
会被忽略(而不会报错)。
2.5 同步代码
当测试同步代码的时候,省略回调函数,Mocha就会自动执行下一条测试。
describe('Array', function () {describe('#indexOf()', function () {it('没有找到时应当返回-1', function () {[1, 2, 3].indexOf(5).should.equal(-1)[1, 2, 3].indexOf(0).should.equal(-1)})})
})
2.6 箭头函数
不建议在Mocha中使用箭头函数(“lambdas”)。由于(箭头函数特殊的)this
绑定语法,箭头函数无法访问Mocha的上下文。例如,下面的代码会因为使用了箭头函数而执行失败:
describe('my suit', () => {it('my test', () => {// 应当设置1000毫秒延迟,而不是执行失败this.timeout(1000)assert.ok(true)})
})
当然如果你不需要使用Mocha的上下文,使用lambdas就没有问题了。然而这样的做的结果是你的测试代码将难以重构
2.7 钩子函数
Mocha默认使用“BDD”风格的接口,提供了before()
,after()
,beforeEach()
和afterEach()
四个钩子函数。这些函数可以用来(在测试前)做预处理工作或在测试后清理工作。
describe('hooks', function () {before(function () {// 在这个作用域的所有测试用例运行之前运行})after(function () {// 在这个作用域的所有测试用例运行完之后运行})beforeEach(function () {// 在这个作用域的每一个测试用例运行之前运行})afterEach(function () {// 在这个作用域的每一个测试用例运行之后运行})// 测试用例
})
测试用例和测试的钩子可以混合排列。(相同的)钩子函数会按照它们的书写顺序运行;(整体的运行顺序是)所有的
before()
钩子运行一次,然后是beforeEach()
钩子,测试用例,afterEach()
钩子(循环运行),最后是after()
钩子(运行一次)
钩子函数的描述参数
所有的钩子在调用时都可以提供一个可选的“描述信息”的参数,以便在你的测试中更精确地定位错误。如果给一个钩子函数传入一个命名函数,当未提供“描述信息”参数的时候,这个命名函数的名称将被作为描述信息。
beforeEach(function () {// beforeEach hook
})beforeEach(function namedFun () {// beforeEach: namedFun
})beforeEach('一些描述信息' ,function () {// beforEach: 一些描述信息
})
异步钩子
所有钩子(before()
, after()
, beforeEach()
, afterEach()
)既可以是同步的也可以是异步的,(这一点上)它们的行为与普通的测试用例非常相似:
describe('连接', function () {var db = new Connection,tobi = new User('tobi'),loki = new User('loki'),jane = newUser('jane')beforeEach(function (done) {db.clear(function (err) {if (err) return done(err)db.save([tobi, loki, jane], done)})})describe('#find()', function () {it('返回匹配的记录', function (done) {db.find({type: 'User'}, function (err, res) {if (err) return done(err)res.should.have.length(3)})})})
})
全局钩子
你可以在任意(测试)文件中添加“全局”级别的钩子函数,例如,在所有describe()
作用域之外添加一个beforeEach()
,它的回调函数会在所有的测试用例运行之前运行,无论(这个测试用例)处在哪个文件(这是因为Mocha有一个隐藏的describe()
作用域,称为“根测试套件 root suite”)。
beforeEach(function () {console.log('在所有文件的所有测试用例之前运行')
})
延迟的根测试套件
如果你需要在所有测试套件运行之前进行一些异步操作,你可以延迟根测试套件。以--delay
参数运行mocha
,这会在全局注入一个特殊的回调函数run()
:
setTimeout(function () {// 一些设置describe('我的测试套件', function () {// ...})run()
}, 5000)
2.8 挂起测试(Pending Tests)
“Pending”——“有人最终会编写这些测试用例”——没有传入回调函数的测试用例:
describe('Array', function () {describe('#indexOf()', function () {// 挂起的测试用例it('未找到时应当返回-1')})
})
挂起的测试用例会在报告中出现“pending”状态。
2.9 独占测试
通过向测试套件或测试用例函数添加.only
后缀,独占特性允许你只运行指定的测试套件或测试用例。下面是一个独占测试套件的例子:
describe('Array', function (){describe.only('#indexOf()', function () {// ...})
})
注意:所有嵌套(在.only
套件中的)测试套件仍旧会运行
下面是一个运行单个测试用例的例子:
describe('Array', function (){describe('#indexOf()', function () {it.only('除非找到否则返回-1', function () {// ...})it('找到后应当返回下标', function () {// ...})})
})
在v3.0.0版本以前,only()
使用字符串匹配来决定哪些测试需要执行。v3.0.0以后的版本only()
可以使用多次来定义测试用例的子集去运行:
describe('Array', function() {describe('#indexOf()', function() {it.only('should return -1 unless present', function() {// 这个测试用例会运行})it.only('should return the index when present', function() {// 这个测试用例也会运行})it('should return -1 if called with a non-Array context', function() {// 这个测试用例不会运行})})
})
你也可以选择多个测试套件:
describe('Array', function() {describe.only('#indexOf()', function() {it('should return -1 unless present', function() {// 这个测试用例会运行})it('should return the index when present', function() {// 这个测试用例也会运行})})describe.only('#concat()', function () {it('should return a new Array', function () {// 这个测试用例也会运行})})describe('#slice()', function () {it('should return a new Array', function () {// 这个测试用例不会运行})})
})
但测试会存在优先级:
describe('Array', function() {describe.only('#indexOf()', function() {it.only('should return -1 unless present', function() {// 这个测试用例会运行})it('should return the index when present', function() {// 这个测试用例不会运行})})
})
注意,如果提供了钩子函数,钩子函数仍会执行
注意不要把
.only
提交到版本控制上,除非你明确知道你在做什么
2.10 跳过测试
这个功能是only()
的反面。通过后缀skip()
就可以让Mocha忽略这个测试套件或测试用例。所有被跳过的测试都会被标记为pending
状态并体现在报告中。下面是一个跳过一整个测试套件的例子:
describe('Array', function() {describe.skip('#indexOf()', function() {// ...})
})
下面是一个跳过测试用例的例子:
describe('Array', function() {describe('#indexOf()', function() {it.skip('should return -1 unless present', function() {// 这个测试用例不会运行})it('should return the index when present', function() {// 这个测试用例会运行})})
})
最佳实践:使用
skip()
而不是直接将测试注释掉
你也可以使用this.skip()
在运行时跳过测试。如果测试需要的环境或配置没办法提前检测,可以考虑使用运行时跳过。例如:
it('应该仅在正确的环境配置中测试', function () {if(/*测试环境正确*/) {// 编写断言} else {this.skip()}
})
因为这个测试什么也没做,它会被报告为passing
。
最佳实践:不要什么也不做!一个测试应当编写断言或者使用
this.skip()
如果想以这种方式跳过多个测试,可以在一个befor()
钩子函数中调用this.skip()
:
before(function() {if (/* check test environment */) {// setup code} else {this.skip()}
})
在Mocha v3.0.0版本以前,钩子函数和异步测试中不支持
this.skip()
2.11 重试测试
你可以选择将失败的测试重试一定的次数。这个特性被设计用于资源(数据)不容易被仿造的端到端(end-to-end)测试(functional tests/Selenium…)。不推荐将这个特性用于单元测试。
这个特性会重新运行beforeEach()
/afterEach()
钩子,但不会运行before()
/after()
钩子。
注意:下面的例子使用了Selenium webdriver(为Promise链式调用改写了Mocha的全局钩子)。
describe('retries', function() {// 测试套件中的所有测试用例将被重试4次this.retries(4)beforeEach(function () {browser.get('http://www.yahoo.com');})it('should succeed on the 3rd try', function () {// 指定这个测试用例仅重试2次this.retries(2)expect($('.foo').isDisplayed()).to.eventually.be.true})
})
2.12 动态生成测试
可以使用Function.prototype.call
和函数表达式来定义测试套件和测试用例,以动态生成测试而不需要其它的特殊语法——简单的JavaScript就能用于实现你可能在其它测试框架中见到过的类似“参数化”测试的功能。
例如:
var assert = require('chai').assertfunction add() {return Array.prototype.slice.call(arguments).reduce(function(prev, curr) {return prev + curr}, 0)
}describe('add()', function() {var tests = [{args: [1, 2], expected: 3},{args: [1, 2, 3], expected: 6},{args: [1, 2, 3, 4], expected: 10}]tests.forEach(function(test) {it('correctly adds ' + test.args.length + ' args', function() {var res = add.apply(null, test.args)assert.equal(res, test.expected)})})
})
上面的代码将会生成一个带有三个测试用例的测试套件:
$ mochaadd()? correctly adds 2 args? correctly adds 3 args? correctly adds 4 args
2.13 测试耗时
许多测试报告都会显示测试耗时,并且标记出那些耗时较长的测试,就像下面的报告显示的那样:
测试耗时
你可以使用slow()
方法来定义到底多久才算“耗时较长”:
describe('something slow', function() {this.slow(10000)it('它的耗时应该足够我去做个三明治了', function() {// ...})
})
2.14 测试超时
套件级别
套件级别的超时应用于整个测试套件,你也可以通过this.timeout(0)
来取消超时限制。如果没有覆盖这个值的话,所有嵌套的测试套件和测试用例都会继承这个超时限制。
describe('a suite of tests', function() {this.timeout(500)it('应当不超过500毫秒', function(done){setTimeout(done, 300)})it('也应当不超过500毫秒', function(done){setTimeout(done, 250)})
})
用例级别
也可以对单一用例设置超时时间,或者通过this.timeout(0)
来取消超时限制:
it('应该不超过500毫秒', function(done){this.timeout(500)setTimeout(done, 300)
})
钩子级别
当然也可以设置钩子级别的超时:
describe('一个测试套件', function() {beforeEach(function(done) {this.timeout(3000); // 一个用时很长的环境设置操作.setTimeout(done, 2500)})
})
同样,使用this.timeout(0)
来取消超时限制
在v3.0.0或更新的版本中,给
this.timeout()
传递一个大于最大延迟值的参数会让超时限制失效
2.15 差异比较
Mocha支持断言库抛出的AssertionErrors
的两个属性err.expected
和err.actual
。Mocha会尝试显示期望(的代码)和断言库真正看到的的代码之间的差异。这里有一个“string”差异的例子:
3. 通配符
命令行指定测试脚本时,可以使用通配符,同时指定多个文件。
$ mocha spec/{my,awesome}.js
$ mocha test/unit/*.js
上面的第一行命令,指定执行spec目录下面的my.js和awesome.js。第二行命令,指定执行test/unit目录下面的所有js文件。
除了使用Shell通配符,还可以使用Node通配符。
$ mocha 'test/**/*.@(js|jsx)'
上面代码指定运行test目录下面任何子目录中、文件后缀名为js或jsx的测试脚本。注意,Node的通配符要放在单引号之中,否则星号(*)会先被Shell解释。
上面这行Node通配符,如果改用Shell通配符,要写成下面这样。
$ mocha test/{,**/}*.{js,jsx}
4. 命令行
Usage: mocha [debug] [options] [files]Commands:init <path> initialize a client-side mocha setup at <path>Options:-h, --help 显示使用帮助-V, --version 显示版本信息-A, --async-only 强制所有测试带有回调(异步)或返回一个promise-c, --colors 强制启用颜色-C, --no-colors 强制关闭颜色-G, --growl 启用弹出消息-O, --reporter-options <k=v,k2=v2,...> 测试报告工具详细设置-R, --reporter <name> 指定测试报告工具-S, --sort 测试文件排序-b, --bail 第一次测试不通过立即结束测试-d, --debug 开启node的debugger模式, 同node --debug-g, --grep <pattern> 只运行匹配<pattern>的测试-f, --fgrep <string> 只运行包含<string>的测试-gc, --expose-gc 暴露gc扩展-i, --invert 反转--grep 和--fgrep 匹配-r, --require <name> 加载指定模块-s, --slow <ms> 以毫秒为单位定义"慢" 测试门槛 [75]-t, --timeout <ms> 以毫秒为单位设置测试用例超时时间 [2000]-u, --ui <name> 指定用户接口 (bdd|tdd|qunit|exports)-w, --watch 监测文件变动--check-leaks 检查全局变量泄露--full-trace 显示完整的跟踪堆栈--compilers <ext>:<module>,... 使用指定的模块编译文件--debug-brk 在首行启用node的debugger断点--globals <names> allow the given comma-delimited global [names](没办法强行翻译了)--es_staging 开启所有过时的特性--harmony<_classes,_generators,...> all node --harmony* flags are available--preserve-symlinks 命令模块加载器在解析和缓存模块时保留符号链接--icu-data-dir 包括ICU数据--inline-diffs 在行内显示实际/预期的字符差异--interfaces 显示可用的接口(bdd|tdd|qunit|exports)--no-deprecation 禁用警告--no-exit 请求一个彻底的事件循环终止: Mocha不会调用 process.exit--no-timeouts 禁用超时, 隐含 --debug--opts <path> 指定选项路径--perf-basic-prof enable perf linux profiler (basic support)--prof 记录统计分析信息--log-timer-events 记录包含外部回调的时间点--recursive 包含子目录--reporters 显示可用的测试报告工具--retries <times> 设置重试未通过的测试用例的次数--throw-deprecation 当使用了废弃的方法时抛出异常--trace 追踪函数借调--trace-deprecation 显示废弃的跟踪堆栈--use_strict 强制严格模式--watch-extensions <ext>,... --watch 上附加的监控扩展--delay 等待异步套件定义
-w
,--watch
初始化后,监测文件变动运行测试
--compilers
CoffeeScript不再被直接支持。这类预编译语言可以使用相应的编译器扩展来使用,比如CS1.6:--compilers coffee:coffee-script
和CS1.7+:--compilers coffee:coffee-script/register
babel-register
如果你的ES6模块是以.js
为扩展名的,你可以npm install --save-dev babel-register
,然后--require babel-register; --compilers
就可以指定文件扩展名
-b
,--ball
只对首个异常感兴趣?使用--bail
-d
,--debug
开启node的调试模式,这会用node debug <file ...>
来执行你的脚本,允许你逐行调试代码并用debugger
声明来打断点。注意mocha debug
和mocha --debug
的区别:mocha debug
会启动node内置的debug客户端,mocha --debug
则允许你使用其它调试工具——比如Blink Developer Tools。
--globals <name>
接受一个以逗号分隔的全局变量名,例如,如果你的应用有意暴露一个全局变量名app
或YUI
,你可能就会使用--globals app,YUI
。它还接受通配符。--globals '*bar'
会匹配foobar, barbar
等。你也可以简单地传入*
来忽略所有全局变量。
check-leaks
在运行测试时,Mocha默认不会检查全局变量泄露,可以使用--check-leaks
来开启这一功能,使用--globals
来指定接受的全局变量比如--globals jQuery,MyLib
。
-r
,--require
<module-name>
--require
选项对诸如should.js一类的库很有用,所以你可以使用--require should
而不是在每一个测试文件中都调用require('should')
。需要注意的是因为should
是增强了Object.prototype
所以可以正常使用,然而假如你希望访问某模块的输出你就只能require
它们了,比如var should = require('should')
。此外,还可以使用相对路径,比如--reqiure ./test/helper.js
-u
,--ui
<name>
--ui
选项让你指定想使用的接口,默认为“bdd”。
-R
,--reporter
<name>
--reporter
选项允许你指定希望使用的报告器,默认为“spec”。这个标记也可以用来使用第三方的报告器。例如,如果你npm install mocha-locv-reporter
,你可以--reporter mocha-locv-reporter
。
-t
,--timeout
<ms>
指定测试用例超时时间,默认为2秒。你可以传入毫秒数或者一个带s
单位后缀的秒数进行覆盖,例如--timeout 2s
和--timeout 2000
是等价的。
s
,--slow
<ms>
指定“慢”测试阈值,默认为75毫秒。Mocha用这个去高亮那些耗时过长的测试。
-g
,--grep
<pattern>
指定--grep
选项让Mocha只运行匹配<pattern>
的测试,<pattern>
将会作为正则表达式进行解析。
假如,像下面的片段那样,你有一些“api”相关的测试和一些“app”相关的测试;则前者可以使用--grep api
来运行,后者可使用--grep --app
来运行
describe('api', function() {describe('GET /api/users', function() {it('respond with an array of users', function() {// ...})})
})describe('app', function() {describe('GET /users', function() {it('respond with an array of users', function() {// ...})})
})
配置文件mocha.opts
Mocha允许在test
目录下面,放置配置文件mocha.opts
,把命令行参数写在里面。例如:
$ mocha --recursive --reporter tap --growl
上面这个命令有三个参数--recursive
、--reporter tap
、--growl
。
然后,把这三个参数写入test
目录下的mocha.opts
文件。
--reporter tap
--recursive
--growl
然后,执行mocha
就能取得与第一行命令一样的效果。
$ mocha
如果测试用例不是存放在test
子目录,可以在mocha.opts
写入以下内容。
server-tests
--recursive
上面代码指定运行server-tests
目录及其子目录之中的测试脚本。
5.接口
Mocha的“接口”系统允许开发者选择习惯的风格或DSL。Mocha有BDD,TDD,Exports,QUnit和Require风格的接口。
5.1 BDD
BDD接口提供describe()
,context()
,it()
,specify()
,before()
,after()
,beforeEach()
和afterEach()
。
context()
只是describe()
的别名,二者表现也是一致的;它只是为了让测试可读性更高。同样specify()
也是it()
的别名。
前文所有的示例都是使用BDD接口编写的
describe('Array', function() {before(function() {// ...})describe('#indexOf()', function() {context('when not present', function() {it('should not throw an error', function() {(function() {[1,2,3].indexOf(4)}).should.not.throw()})it('should return -1', function() {[1,2,3].indexOf(4).should.equal(-1);})})context('when present', function() {it('should return the index where the element first appears in the array', function() {[1,2,3].indexOf(3).should.equal(2)})})})
})
5.2 TDD
TDD接口提供suite()
,test()
,suiteSetup()
,suiteTeardown()
,setup
和teardown()
:
suite('Array', function() {setup(function() {// ...})suite('#indexOf()', function() {test('should return -1 when not present', function() {assert.equal(-1, [1,2,3].indexOf(4));})})
})
5.3 EXPORTS
EXPORTS接口很像Mocha的前身expresso,键值before
,after
,beforeEach
,afterEach
是特殊用例,对象类型的属性值是测试套件,方法类型的属性值是测试用例:
module.exports = {before: function() {// ...},'Array': {'#indexOf()': {'should return -1 when not present': function() {[1,2,3].indexOf(4).should.equal(-1)}}}
}
5.4 QUNIT
类QUnit接口与QUnit的“扁平化”外观相匹配,测试套件只需要简单地在测试用例之前定义就行。和TDD类似,它使用suite()
和test()
,但又类似于BDD,也包含了before()
,after()
,beforeEach()
和afterEach()
。
function ok(expr, msg) {if (!expr) throw new Error(msg)
}suite('Array')test('#length', function() {var arr = [1,2,3]ok(arr.length == 3)
})test('#indexOf()', function() {var arr = [1,2,3]ok(arr.indexOf(1) == 0)ok(arr.indexOf(2) == 1)ok(arr.indexOf(3) == 2)
})suite('String')test('#length', function() {ok('foo'.length == 3)
})
5.5 REQUIRE
require
风格接口允许你直接用require
语句引入describe
等函数并在任意位置使用它们。当你希望在你的测试中禁用全局变量时这也会很有用。
注意,require
风格接口不能通过node直接运行,必须通过Mocha运行
var testCase = require('mocha').describe
var pre = require('mocha').before
var assertions = require('mocha').it
var assert = require('chai').asserttestCase('Array', function() {pre(function() {// ...});testCase('#indexOf()', function() {assertions('should return -1 when not present', function() {assert.equal([1,2,3].indexOf(4), -1)})})
})
6. 测试报告
Mocha的测试报告与命令行窗口适配,且当标准输出串口没有关联到打印机时始终禁用ANSI-escape颜色。
6.1 SPEC
这是默认的测试报告。“SPEC”测试报告输出与测试用例一致的嵌套视图。
spec 测试报告
带有失败状态的spec 测试报告
6.2 Dot Matrix
Dot Matrix(或者Dot)测试报告使用一串简单字符来表示测试用例,失败的(failing)以红色叹号(!)表示,挂起的(pedding)以蓝色逗号(,)表示,长时的以黄色表示。当你希望最小化输出的时候这就很好。
dot matrix 测试报告
6.3 NYAN
NYAN测试报告就是你想的那样(一只猫):
js nyan cat 测试报告
6.4 TAP
TAP测试报告的输出很适合Test-Anything-Protocol的用户。
test anything protocol
6.5 Landing Strip
landing Strip(landing)测试报告是一个非正式的测试报告器,它用unicode字符模仿了飞机降落的情景。
landing strip plane 测试报告
带失败状态的landing strip测试报告
6.6 LIST
list测试报告输出一个测试用例通过或失败的简洁列表,并在底部输出失败用例的详情。
list测试报告
6.7 PROGRESS
progress测试报告展示一个简单进度条。
progress bar
6.8 JSON
JSON测试报告在测试完成后输出一个大JSON对象。
json reporter
6.9 JSON STREAM
JSON stream测试报告输出根据“事件”断行了的JSON,以“start”事件开始,紧跟着测试通过或失败,最后是“end”事件。
json stream reporter
6.10 MIN
min测试报告仅显示结果摘要,当然也输出失败时的错误信息。当与--watch
一起使用时很棒,它会清空你的命令行让测试摘要始终显示在最上方。
min reporter
6.11 DOC
doc测试报告输出一个层级化的HTML来表示你的测试结果。使用header,footer和一些样式来包裹测试结果,然后你就有了一份惊艳的测试报告文档!
doc reporter
例如,假定你有下面的JavaScript:
describe('Array', function() {describe('#indexOf()', function() {it('should return -1 when the value is not present', function() {[1,2,3].indexOf(5).should.equal(-1);[1,2,3].indexOf(0).should.equal(-1);});});
});
在命令行输入mocha --reporter doc array
会输出:
<section class="suite"><h1>Array</h1><dl><section class="suite"><h1>#indexOf()</h1><dl><dt>should return -1 when the value is not present</dt><dd><pre><code>[1,2,3].indexOf(5).should.equal(-1);
[1,2,3].indexOf(0).should.equal(-1);</code></pre></dd></dl></section></dl>
</section>
存储为文件
$ mocha --recursive -R doc > spec.html
使用此Bash命令,使用Mocha的doc reporter生成了SuperAgent请求库测试文档:
$ mocha --reporter=doc | cat docs/head.html - docs/tail.html > docs/test.html
查看SuperAgent
的Makefile 以供参考。
6.12 MARKDOWN
markdown测试报告为你的测试单元能生成一个markdown TOC。当你希望将你的测试结果放在Github的wiki或仓库中时,这会很好用。
$ mocha --recursive -R markdown > spec.md
6.13 HTML
HTML测试报告是当前Mocha唯一支持的浏览器测试报告,类似这样:
HTML test reporter
6.14 未记载的测试报告
XUnit测试报告也是可以用的。默认地,它输出到console。想直接写入文件,使用--reporter-options output=filename.xml
。
6.15 第三方的测试报告
Mocha允许自定义第三方的测试报告生成器。浏览wiki获取更多信息。一个例子是TeamCity reporter。
7. 在浏览器中运行Mocha
Mocha可以运行在浏览器中。Mocha的每个释出版本都会有./mocha.js
和./mocha.css
来在浏览器中使用。
7.1 基本用法
首先,使用mocha init命令在指定目录生成初始化文件。
$ mocha init demo
运行上面命令,就会在demo目录下生成index.html文件,以及配套的脚本和样式表。
载入测试脚本,在onload
中用mocha.run()
运行它们之前,我们调用mocha.setup('bdd')
来使用BDD风格的接口。
<html>
<head><meta charset="utf-8"><title>Mocha Tests</title><link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
</head>
<body><div id="mocha"></div><script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script><script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script><script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script><script>mocha.setup('bdd')</script><script src="test.array.js"></script><script src="test.object.js"></script><script src="test.xhr.js"></script><script>mocha.checkLeaks();mocha.globals(['jQuery']);mocha.run();</script>
</body>
</html>
现在,在浏览器里面打开index.html,就可以看到测试脚本的运行结果。
7.2 浏览器专用方法
下面的方法仅在浏览器环境中有效:
mocha.allowUncaught()
:如果调用,未捕获的错误不会被error handler处理。
7.3 grep
浏览器中也可以使用--grep
功能。在你的URL上添加一个请求参数:?grep=api
。
7.4 浏览器配置
Mocha的选项可以通过mocha.setup()
来配置。比如:
// Use "tdd" interface. This is a shortcut to setting the interface;
// any other options must be passed via an object.
mocha.setup('tdd');// This is equivalent to the above.
mocha.setup({ui: 'tdd'
});// Use "tdd" interface, ignore leaks, and force all tests to be asynchronous
mocha.setup({ui: 'tdd',ignoreLeaks: true,asyncOnly: true
});
7.5 浏览器专用选项
下面的选项仅在浏览器环境有效:
noHighlighting
:如果设置为true
,do not attempt to use syntax highlighting on output test code。
mocha.opts
回到服务器,Mocha会试图加载./test/mocha.opts
作为Mocha的配置文件。文件是由命令行参数按行拼接起来的。命令行参数也是有优先级的,例如,假定你有下面的mocha.opt
文件:
--require should
--reporter dot
--ui bdd
它会将默认测试报告设置为dot,加载should
断言库,并使用BDD风格的接口。然后你可能会继续带参数运行Mocha,这里是开启Growl支持,并将测试报告更换为list:
$ mocha --reporter list --growl
8. test/
文件夹
Mocha默认会全局寻找./test/*.js
和./test/*.coffee
,所以你可能需要将你的测试文件放到./test
文件夹中
9. 编辑器插件
下面的编辑器插件package可用:
9.1 TextMate
Mocha的TextMate包包含了能够加速测试编写的代码片段。克隆Mocha repo并运行make tm
来安装这个包
9.2 JetBrains
JetBrains为它们的IDE套件(IntelliJ IDEA,WebStorm等)提供了一个NodeJS插件,包含了一个Mocha test runner,和一些周边。
运行中的JetBrains Mocha Runner Plugin
插件名为NodeJS,并且可以通过Preference > Plugins来安装…如果你的许可允许的话。
9.3 Wallaby.js
Wallaby.js是一个持续测试工具,为JetBrains IDE和Visual Studio中的Mocha提供实时的测试覆盖率,不管是运行在node.js还是浏览器的项目。
运行中的Wallaby.js
9.4 Emacs
Emacs支持通过第三方插件mocha.el来运行Mocha测试。插件可以在MELPA上找到,也可通过M-x package-install mocha
来安装。
运行中的Emacs Mocha Runner
10. Http测试
用mocha测试一个async函数是非常方便的。现在,当我们有了一个koa的Web应用程序时,我们怎么用mocha来自动化测试Web应用程序呢?
一个简单的想法就是在测试前启动koa的app,然后运行async测试,在测试代码中发送http请求,收到响应后检查结果,这样,一个基于http接口的测试就可以自动运行。
我们先创建一个最简单的koa应用,结构如下:
koa-test/
|
+- .vscode/
| |
| +- launch.json <-- VSCode 配置文件
|
+- app.js <-- koa app文件
|
+- start.js <-- app启动入口
|
+- test/ <-- 存放所有test
| |
| +- app-test.js <-- 异步测试
|
+- package.json <-- 项目描述文件
|
+- node_modules/ <-- npm安装的所有依赖包
这个koa应用和前面的koa应用稍有不同的是,app.js
只负责创建app
实例,并不监听端口:
// app.jsconst Koa = require('koa');const app = new Koa();app.use(async (ctx, next) => {const start = new Date().getTime();await next();const ms = new Date().getTime() - start;console.log(`${ctx.request.method} ${ctx.request.url}: ${ms}ms`);ctx.response.set('X-Response-Time', `${ms}ms`);
});app.use(async (ctx, next) => {var name = ctx.request.query.name || 'world';ctx.response.type = 'text/html';ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});module.exports = app;
而start.js
负责真正启动应用:
// start.jsconst app = require('./app');app.listen(3000);
console.log('app started at port 3000...');
这样做的目的是便于后面的测试。
紧接着,我们在test
目录下创建app-test.js
,来测试这个koa应用。
在测试前,我们在package.json
中添加devDependencies
,除了mocha外,我们还需要一个简单而强大的测试模块supertest
:
{..."devDependencies": {"mocha": "3.0.2","supertest": "3.0.0"}
}
运行npm install
后,我们开始编写测试:
// app-test.jsconstrequest = require('supertest'),app = require('../app');describe('#test koa app', () => {let server = app.listen(9900);describe('#test server', () => {it('#test GET /', async () => {let res = await request(server).get('/').expect('Content-Type', /text\/html/).expect(200, '<h1>Hello, world!</h1>');});it('#test GET /path?name=Bob', async () => {let res = await request(server).get('/path?name=Bob').expect('Content-Type', /text\/html/).expect(200, '<h1>Hello, Bob!</h1>');});});
});
在测试中,我们首先导入supertest
模块,然后导入app
模块,注意我们已经在app.js
中移除了app.listen(3000);
语句,所以,这里我们用:
let server = app.listen(9900);
让app实例监听在9900
端口上,并且获得返回的server
实例。
在测试代码中,我们使用:
let res = await request(server).get('/');
就可以构造一个GET
请求,发送给koa
的应用,然后获得响应。
可以手动检查响应对象,例如res.body
,还可以利用supertest
提供的expect()
更方便地断言响应的HTTP代码、返回内容和HTTP头。断言HTTP头时可用使用正则表达式。例如,下面的断言:
.expect('Content-Type', /text\/html/)
可用成功匹配到Content-Type
为text/html
、text/html
; charset=utf-8
等值。
当所有测试运行结束后,app实例会自动关闭,无需清理。
利用mocha
的异步测试,配合supertest
,我们可以用简单的代码编写端到端的HTTP自动化测试。