简介
- Mocha是流行的JavaScript测试框架之一,通过它添加和运行测试,从而保证代码质量
- 既可以测试简单的JavaScript函数,又可以测试异步代码,因为异步是JavaScript的特性之一
- 可以自动运行所有测试,也可以只运行特定的测试;
- 可以支持before、after、beforeEach和afterEach来编写初始化代码。
- describe块称为"测试套件"(test suite),表示一组相关的测试,可以有多个
- chai 断言库(node里也有自带的assert)
- sinon 可以单独测试spies, stubs and mocks,适用于任何的测试框架
编写测试
npm init --y
npm i mocha chai -S
// src/myClass.js
class MyClass{
constructor(){
console.log('initiate')
}
add(arg1, arg2){
const res = arg1+arg2
return res
}
}
module.exports = MyClass
// myClass.spec.js
const MyClass = require('../src/myClass')
const myObj = new MyClass()
const chai = require('chai')
const expect = chai.expect
describe('Test suit', ()=>{
it('Test the add method',()=>{
expect(myObj.add(1, 2)).to.be.equal(3)
})
})
// package.json
"scripts": {
"test": "mocha './specs/**/*.spec.js'" // mocha './specs/*.@(js|jsx)'
}
chai是什么?
// 测试失败用抛异常来处理,有点繁琐,所以就有了断言库的出现(chai断言库)
describe('Test suit', ()=>{
it('Test the add method',()=>{
if(myObj.add(1, 2)!==3){
throw new Error('错误~~~'’);
}
})
})
Spy
它可以追踪
谁进入你家?
它进入多少次?
它携带了什么?
它什么时候离开?
它离开的时候做了什么?
npm i sinon -D
// src/myClass.js
class MyClass{
constructor(){
console.log('initiate')
}
add(arg1, arg2){
const res = arg1 + arg2
return res
}
callAnotherFn(arg1, arg2){
return this.add(arg1, arg2)
}
callTheCallback(callback){
callback()
}
}
module.exports = MyClass
// myClass.spec.js
// ...
const sinon = require('sinon')
describe('Test suit', ()=>{
// ...
it('spy the add method',()=>{
const spy = sinon.spy(myObj, 'add') // 追踪add函数是否被调用
myObj.callAnotherFn(1, 2)
// 断言add函数在里面是否被执行一次
// sinon.assert.calledOnce(spy)
expect(spy.calledTwice).to.be.false
expect(spy.calledWith(1, 2)).to.be.true
})
it('spy the callback method',()=>{
const spy = sinon.spy() // 设置一个追踪器
myObj.callTheCallback(spy)
expect(spy.calledOnce).to.be.true
})
})
Mock
受到前面测试函数的影响,会被执行两次,
所以要创建mock,不会改变实际的对象,只是创建一个包装器
// src/myClass.js
class MyClass{
constructor(){
console.log('initiate')
}
add(arg1, arg2){
const res = arg1 + arg2
console.log(res)
return res
}
sayHello(str){
console.log(str)
}
callAnotherFn(arg1, arg2){
this.sayHello()
return this.add(arg1, arg2)
}
callTheCallback(callback){
callback()
}
}
module.exports = MyClass
// myClass.spec.js
describe('Test suit', ()=>{
//...
// it('mock the sayHello method',()=>{
// myObj.callAnotherFn(3, 4)
// })
it('mock the sayHello method',()=>{
const mock = sinon.mock(myObj)
const expetcation = mock.expects('sayHello')
expetcation.exactly(1)
expectation.withArgs('hello world 111')
myObj.callAnotherFn(3, 4)
mock.verify() // 验证对模拟的所有期望
})
})
Stub
// myClass.spec.js
describe('Test suit for stub', ()=>{
it('stub the add method', ()=>{
let stub = sinon.stub(myObj, 'add') // 用来替换myObj.add函数,自定义返回结果(模拟add函数)
stub
.withArgs(10, 20)
.onFirstCall()
.returns(100) // 定义第一次执行时的返回结果为100
.onSecondCall()
.returns(200) // 定义第二次执行时的返回结果为200
expect(myObj.callAnotherFn(10, 20)).to.be.equal(100) // 测试返回结果
expect(myObj.callAnotherFn(10, 20)).to.be.equal(200)
})
})
Test a Promise
npm i chai-as-promised
// src/myClass.js
class MyClass{
// ...
testPromise(){
return new Promise((resolve, reject)=>{
setTimeout(() => resolve(3), 4000)
}).then(result => {
return result*2
})
}
}
// myClass.spec.js
const chaiaspromise = require("chai-as-promised")
chai.use(chaiaspromise)
describe.skip("Test the promise", function() {
it("Promise test case", function(done) {
this.timeout(4000)
myObj.testPromise().then(function(result) {
expect(result).to.be.equal(6)
done()
})
})
})
describe("Test the promise", function() {
it("Promise test case with chai-as-promised", function() {
this.timeout(0)
return expect(myObj.testPromise()).to.eventually.equal(6)
})
})
xhr
npm i nock -D
npm i xmlhttprequest -S
//
Hooks
// myClass.spec.js
describe('Test suit', ()=>{
after(function() {
console.log("===================== After the test suit")
})
before(function() {
console.log("===================== Before the test suit")
})
afterEach(function() {
console.log("======= After every test case inside this test suit")
})
beforeEach(function() {
// Sinon包装必须在测试用例之前或之后恢复
sinon.restore()
console.log("======= Before every test case inside this test suit")
})
it('Test the add method',()=>{
expect(myObj.add(1, 2)).to.be.equal(3)
})
it('spy the add method',()=>{
const spy = sinon.spy(myObj, 'add') // 追踪add函数是否被调用
myObj.callAnotherFn(1, 2)
// 断言add函数在里面是否被执行一次
// sinon.assert.calledOnce(spy)
expect(spy.calledTwice).to.be.false
expect(spy.calledWith(1, 2)).to.be.true
})
})
/* 输出
===================== B
efore the test suit
======= Before every test case inside this test suit
3
✔ Test the add method
======= After every test case inside this test suit
======= Before every test case inside this test suit
3
✔ spy the add method
======= After every test case inside this test suit
===================== After the test suit
*/
Coverage
npm i nyc -D
// package.json
"scripts": {
"coverage": "nyc --reporter=text npm run test"
"coverage": "nyc --reporter=lcov --reporter=text npm run test" // lcov 生成报告
},
/* "nyc":{
"include": "src", // 测试覆盖率只测试src文件夹
"exculed": "specs"
},
*/
// 或者建立外部文件
// .nycrc.json
{
"include": "src",
"exclude": "specs",
"check-coverage": true,
"branches": 80,
"lines": 80,
"functions": 80,
"statements": 80,
"report-dir": "./reports",
"skip-full": true
}