单测
前端所进行的自动化测试,一般是指针对函数,类或单个组件,不涉及系统和集成所进行的白盒测试,单元测试是软件测试的基础测试,主要是用来验证所测代码是否和程序员的期望一致。
我所采用的环境是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:) 🎉