Sinon: spies, stubs and mocks
sinon 是一个必不可少的单测辅助框架, 可以帮助我们来测试那些难以测试的函数和方法, 比如后台请求, 异步, 或者依赖于其他系统的函数等.
其中spy,stub,mock是三个测试领域的概念, 用来在不同场景下对方法进行测试.
spy
spy 是一个间谍函数, 它会记录下函数调用的参数, 返回值, this的值, 以及抛出的异常. 间谍函数一般用来帮助我们验证某些事物, 比如是否调用了函数, 调用的情况, 返回值等. 并且是一种窃听机制, 函数内部是无感知的.
间谍函数最常用的场景, 比如检查函数被调用了多少次, 检查传递给函数的参数等.
spy 一般有两种用法, 一个是生成新的间谍函数, 另外一个是对原有的函数进行封装并进行监听.
//测试的函数
export function once(fn: Function) {
var returnValue: any,
fn_called = false;
return function() {
if (!fn_called) {
fn_called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
//测试用例1: 生成一个测试函数, 观察这个函数的调用情况
it('传入Once的函数被调用有且只有一次', () => {
let callback = sinon.spy();
var proxy = once(callback);
proxy();
proxy();
proxy();
expect(true).to.be.equal(callback.called);
});
//测试用例2: 在一个函数中注入spy, 观察这个函数的调用情况
it('对原有函数的spy封装, 可以监听原有函数的调用情况', () => {
const obj = {
func: (number = 2) => {
return 1 + 1;
}
};
let spy = sinon.spy(obj, 'func');
obj.func(3);
expect(true).to.be.equal(spy.withArgs(3).calledOnce);
});
stub
stub 是带有预编程行为的函数, 简单点说, 就是 spy 的加强版, 不仅完全支持 spy 的各种操作, 还能操作函数的行为. 一样的, stub 也可以匿名, 或者注入到其他函数中. 并且经过封装的函数, 原函数不会再运行.
stub 也叫存根函数, 除了替换目标的功能, 还能自定义行为, 例如返回值, 抛出异常, 甚至可以作为参数提供给任何回调函数.
常用场景:
- 代替有问题的代码段
- 触发不会触发的代码路径, 比如错误处理
- 更简单的测试异步代码
it('对原有函数的stub封装,可以监听原有函数的调用情况,以及模拟返回', function() {
const obj = {
func: (number: number) => {
console.info(1);
return 3;
}
};
let stub = sinon.stub(obj, 'func');
stub.returns(42);
let result = obj.func(3);
expect(true).to.be.equal(stub.calledOnce);
expect(42).to.be.equal(result);
expect(3).to.be.equal(stub.getCall(0).args[0]);
});
可以在测试中看到确实没有运行原来的函数, 没有打印出1.
注意: sinon.stub() 完成后一定要 sinon.restore(),否则会影响其他的测试
mock
如果你需要替换某个对象的多个方法, 你就应该使用 mocks, 如果你只希望替换某个单独的方法, stub 更方便.
mocks 内建断言.
it('对象函数至少调用2两次', function() {
var obj = {
f: (number: number) => {
return number;
}
};
var mock = sinon.mock(obj);
mock.expects('f')
.atLeast(2)
.withArgs(10); // obj.f(10)调用至少出现过2次
obj.f(10);
obj.f(10);
mock.verify(); // 测试此时的obj是否满足上面的mock设定条件
mock.restore();
});
enzyme: react 测试库
上面的项目中, 我们针对的是原生项目进行 mocha 测试, 针对 react/vue 这些前端框架, 开发者们也提供可一些针对性的测试框架来配合使用.
Enzyme 是一个针对 react 的 js 单元测试框架, 使针对 react 的组件的单元测试更容易的来编写.
注意: 如果是 react16 以前的版本, 使用 2.x 的版本, 如果是 16 以后的版本, 则需要使用 3.x 的版本.
在使用 enzyme 之前, 先给我们的 demo 库添加 react 依赖和一些基础文件处理:
npm install --save @types/react @types/react-dom @types/react-router-dom react react-dom react-router-dom
基础的图片, 样式, 字体等:
npm install --save css-loader file-loader
然后正在相应的 webpack 中配置:
{
test: /\.(jpe?g|png|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8k的图片自base64格式,并且不会存在实体图片
outputPath: 'images/' // 图后存放的目录
}
}
]
},
{
test: /\.(eot|ttf|woff|svg)$/,
use: 'file-loader'
},
在src中创建一个文件 Home, 添加一个Home组件:
import * as React from 'react';
export interface IHomeProps {}
export default class Home extends React.Component<IHomeProps> {
componentDidMount() {}
public render() {
return <div>HOME</div>;
}
}
在index.ts中挂载:
ReactDom.render(<Home />, document.getElementById('root'));
然后来写一些测试吧,在test文件夹下新建react.spec.tsx:
import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import Home from '../src/pages/Home/Home';
import * as Enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
spy(Home.prototype, 'componentDidMount');
describe('react 测试用例', () => {
it('calls componentDidMount', () => {
const home = Enzyme.shallow(<Home />);
expect(Home.prototype.componentDidMount).to.have.property('callCount', 1);
});
});
可以在运行中看到测试运行的结果.
Enzyme 的几个 API 都比较易懂, 这里限于篇幅, 就不展开介绍了, 官方文档地址, 在这里