单测的意义
对于一些基础的功能函数和通用组件,单测毫无疑问可以提高我们代码的质量以及健壮性。在时间允许的情况下,是应该在开发的同时进行单测代码的编写。然而很多时候,开发人员编写单测仅仅是为了KPI而已。
如果在项目都已经开发完成上线之后,再来补充单测,单纯的追求一个项目整体文件的单测覆盖率,在我看来是没有太大意义的。
单测的核心目的,是为了提高开发时的代码质量和健壮性。而不是说我为了提高当前的项目质量,要求进行单测。如果是这种情况,对于开发来说是一种折磨。会存在一下一些不能避免的问题:
- 你发现老代码的代码结构不合理,你可能想要重构
- 你发现了逻辑bug
- 由于业务变更,代码中有些地方无法通过简单的mock来实现覆盖,来达到计划的文件代码分支覆盖率的要求
- 对于使用了高级运算符的条件语句,分支覆盖很难完全覆盖(比如三元表达式中嵌套了?.操作符
let a = b?.c ? b?.c?.d : 1)
如果遇到了上述问题,我想你一定会很头大。重构和更改现有逻辑代码,都可能会对现网产生未知的bug,你的优化可能是一个负优化。这在开发阶段可以通过测试的全量测试发现这些bug,但是在你写单测时,一般不会给你安排测试进行跟进的。这个时候就需要你对编写单测的模块足够的熟悉,以及足够的信心,在提交代码前最好还要进行相应的详细测试。想想就累。
好了吐槽了那么多,咱身为打工人,很多事不是咱能决定的。下面我们聊聊编写单测中的一些常见场景以及小技巧。
笔者平时开发使用的是React,所以下面我就讲讲Jest + React Testing Library 这套技术栈中常遇的问题和技巧。
常见问题
1. Jest + Enzyme 还是 Jest + React Testing Library
都已经2022了,还是使用Jest + React Testing Library,相比Enzyme而言,React Testing Library更符合测试的理念,我们要测试组件的功能而不是测试具体实现。
具体对比可以参考此文
2. 如何api接口返回数据?
app.tsx
import React from 'react';
import { getCommonQuestions } from './api';
const App = () => {
useEffect(() => {
let lest = getCommonQuestions().then(() => {
// ...
});
// ....
}, []);
// ....
}
可以按照如下方式进行mock
./__test__/app.test.jsx
import _defaultActions from '../actions';
jest.mock('./actions', () => {
const originalModule = jest.requireActual('../actions');
return {
__esModule: true,
...originalModule,
getCommonQuestions: () =>
Promise.resolve({
question: [1, 2, null],
}),
};
});
如此就完成了对该api的mock, 这里有几个点需要注意下
-
import _defaultActions from '../actions';这行代码是必须的,而且注意代码路径是相对于当前文件夹的,他是告诉jest这个路径的模块我会进行mock, 不要引入原路径的代码 -
getCommonQuestions 的返回结果是一个promise
3. 如何mock函数组件
app.tsx
import React from 'react';
import RightBlock from './rightBlock';
const App = () => {
const addClk = () => {
//...
}
return <div>
<RightBlock addClk={addClk} />
</div>
}
./__test__/app.test.jsx
import RightBlock from '../rightBlock';
jest.mock('../rightBlock', () => {
return jest.fn();
});
RightBlock.mockImplementation((props) => {
const { addClk } = props;
useEffect(() => {
addClk?.();
}, []);
return <div>123</div>
});
同前面列子一样,注意模块引入的路径。
4. 如何mock React Hooks
直接使用@testing-library/react-hooks这个库
这里抄一下官方的列子
useCounter.js
import { useState, useCallback } from 'react'
function useCounter() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount((x) => x + 1), [])
return { count, increment }
}
export default useCounter
useCounter.test.js
import { renderHook, act } from '@testing-library/react-hooks'
import useCounter from './useCounter'
test('should increment counter', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
相关资源链接: