【前端测试与集成】使用mocha和sinon进行单元测试

简介

  • 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
}

github参考代码

参考:
mocha官网
详细的mocha
sinon官网

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值