Jest + React Testing library

430 阅读3分钟

单测的意义

对于一些基础的功能函数和通用组件,单测毫无疑问可以提高我们代码的质量以及健壮性。在时间允许的情况下,是应该在开发的同时进行单测代码的编写。然而很多时候,开发人员编写单测仅仅是为了KPI而已。

如果在项目都已经开发完成上线之后,再来补充单测,单纯的追求一个项目整体文件的单测覆盖率,在我看来是没有太大意义的。

单测的核心目的,是为了提高开发时的代码质量和健壮性。而不是说我为了提高当前的项目质量,要求进行单测。如果是这种情况,对于开发来说是一种折磨。会存在一下一些不能避免的问题:

  1. 你发现老代码的代码结构不合理,你可能想要重构
  2. 你发现了逻辑bug
  3. 由于业务变更,代码中有些地方无法通过简单的mock来实现覆盖,来达到计划的文件代码分支覆盖率的要求
  4. 对于使用了高级运算符的条件语句,分支覆盖很难完全覆盖(比如三元表达式中嵌套了?.操作符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更符合测试的理念,我们要测试组件的功能而不是测试具体实现。

具体对比可以参考此文

techdoma.in/react-js-te…

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, 这里有几个点需要注意下

  1. import _defaultActions from '../actions'; 这行代码是必须的,而且注意代码路径是相对于当前文件夹的,他是告诉jest这个路径的模块我会进行mock, 不要引入原路径的代码

  2. 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)
})

相关资源链接:

  1. github.com/testing-lib…
  2. testing-library.com/docs/
  3. jestjs.io/zh-Hans/