Bermu

再也不想写的测试用例 😤 [Jest+Enzyme+Sinon]

2019-01-18

单测

前端所进行的自动化测试,一般是指针对函数,类或单个组件,不涉及系统和集成所进行的白盒测试,单元测试是软件测试的基础测试,主要是用来验证所测代码是否和程序员的期望一致。


  • 我所采用的环境是React

  • 引入Facebook的jest

  • 再引入Airbnb的enzyme


通过npm或者yarn都可以快速地进行配置,VScode可以默认自带Jest调试环境,

其他IDE可以手动输入


1
jest --colors --coverage


可以生成带有覆盖率报告的生成文件,或者你也可以在package.json中配置指令,

生成的报告文件可以在根目录找到covarage目录,详细地查看代码中没有触发的情况。


来说一说遇到的一些小问题 😷

Jest 在 package.json 中添加的配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
"devDependencies": "",
"jest": {
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/config/fileMock.js",
"\\.(css|less)$": "identity-obj-proxy"
},
"setupFiles": [
"<rootDir>/src/setupTests.js"
]
}
...


* moduleNameWrapper 是需要mock豁免的文件
* setUpFiles 顾名思义启动文件了


那么这个启动文件往往需要配置一些东西


1
2
3
4
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() })

以上代码段主要是配置enzyme的兼容,注意官方文档给出的api是匹配 chai 的 ,所以套用enzyme的api时如果没有完成环境的搭建,就会出现🐛,不过你要是不想搞这么多配置,也👌,那就是mix混合使用,可以先拿enzyme中

的核心模块JSDOM调试,find到某个节点后再用jest的断言。

浩浩荡荡的测试 🦑

渲染类型

1
2
3
4
5
6
7
8
import {
shallow
} from "enzyme";

it('renders Component', () => {
const wrapper = shallow(<Component/>);
expect(wrapper.find('.dom').length).toBe(1);
});

这就是一个简单的测试啦,是不是看上去也没那么复杂?但是不是所有的测试都是这样一步到位,

官方提供了快照测试、UI测试等等,我们只选取了最直接的测试用例编写。


生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sinon from 'sinon';
import {
shallow
} from "enzyme";
import LineChart from 'LineChart';

describe('component will receive props', () => {
it('Component isn't undefined', () => {
let wrapper = shallow(<LineChart chart={''} />);
sinon.spy(LineChart.prototype, 'componentWillReceiveProps');
wrapper.setProps({
chart: [...data]
});
expect(LineChart.prototype.componentWillReceiveProps.calledOnce).toBeTruthy();
setTimeout(() => {
expect(wrapper.instance().initChart).toBeCalled();
}, 100);
});

afterEach(() => {
// Restore the default sandbox here
sinon.restore();
});
})


有没有发现什么不同,没错!这里采用了sinon,这是因为在真实的项目中,我们的代码经常要做各种导致我们测试很难进行的事情。Ajax请求,timer,日期,跨浏览器特性,面对数据库,网络,文件操作等往往会变得棘手,为此我们引入sinon来解决较为复杂的生命周期处理。


@ Tips 为什么Sinon很重要?


简单的说,Sinon允许你去替换代码中复杂的部分,以此来简化你的测试代码。当我们

测试某部分代码时,你不希望受到其它部分的影响。如果有外部因素影响测试

,那么测试项将变得非常复杂且不稳定。如果你想测试一个使用了ajax的代码,你该怎么做?

你需要跑一个服务端,并保证该服务端返回指定的响应数据来支撑你的测试项。

这很难完成也让运行测试很麻烦。


那如果你的代码依赖时间呢?假如它需要等待一秒钟才执行。怎么办?你需要在你的测试项中使用setTimeout,但这会让测试变得缓慢。想像一下,如果间隔时间很久,例如五分钟。我想你不会希望每次跑测试项都等待五分钟吧。

如果使用Sinon,我们可以搞定这些问题(甚至更多),并减少复杂度。


官方的API中给的很详尽,网上其他博客的文档也很全面,本文就不对timer、fetch等做解释了,去找sinon,

完美解决你的问题。😎


路由变化以及调取实例方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
describe("when pathname is /stat'", () => {
beforeAll(() => {
delete window.location;
window.location = {
pathname: '/stat'
};
});

    it('initchart', () => {
const wrapper = shallow(<LineChart/>);
wrapper.instance().initChart();
setTimeout(() => {
expect(wrapper.instance().initChart).toBeCalled();
}, 100);

const mockDatas = [...datas];

wrapper.instance().initChart(mockDatas);
setTimeout(() => {
expect(wrapper.instance().initChart).toBeCalled();
}, 100);
});
});


以上代码段主要是针对路由变化时需要先删掉window.location这个变量,改为重新set才能生效,别问为什么,

我在Stack Overflow找到的。


模拟某个事件

1
2
3
4
5
6
7
8
9
10
it("Select set value", () => {
const onChange = jest.fn();
const wrapper = shallow(
<Stat onChange={onChange}/>
);
wrapper.find(".list-select").simulate("change", 1);
setTimeout(() => {
expect(onChange).toHaveBeenCalled();
}, 1000);
});


通过simulate来触发代理的mock函数,即可模拟事件状态


props传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
describe('pickdate component called', () => {
const setup = () => {
// 组件的props
const props = {
referProps: jest.fn() // mock
};

const wrapper = mount(
<Router>
<PickDate {...props} />
</Router>
);
return {
props,
wrapper
};
};

it("PickDate receive props", () => {
const {wrapper, props} = setup();
// mock event object
const mockEventObj = {
key: 'Enter',
value: 'TEST'
};
wrapper.find('.date-pick').at(0).prop('onChange')(mockEventObj['key'], mockEventObj['value']);
expect(props.referProps).toBeCalled();
});
});


这种适用于以下情景:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Parent:

<ParentComponent referProps=(props)=>{this.setProps(props)}

Child:


class ChildComponent extends React.Component {
...
setValue(value) {
this.props.referProps(value);
}
    ...
}

<ChildComponent onChange=(value)=>{this.setValue(value)}}`


以上基本是常用场景的覆盖,如果想加入UI自动化测试,参考引入puppeteer无头浏览器进行黑盒测试,




不过我真的不想写这玩意了,分支太多,条件太多,巨烦无比,好像还有UI化的测试工具,好像叫majestic ,有兴趣自己试试吧,反正我是玩崩了。


好处也是有的,让我看到了自己重复写了一些冗余的逻辑,如果你对代码可读性要求很高,测试是必须要搞得。

cheers:) 🎉

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章