React-Typescript 结合 Jest && React Testing Library 单元测试

2,435 阅读4分钟

ChMkKmBamS-IdsvVABQVFnzWDOwAAL-UACX_LYAFBUu002.jpg 这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

概念

  • Jest: Jest是一套测试框架,被Facebook用来测试包括React应用在内的所有JavaScript代码。Jest的一个理念是提供一套完整集成的 “零配置” 测试体验。
    • 官方推荐
    • 支持输出详细的代码覆盖率报告
    • 强大的mock函数
    • 灵活方便引入第三方库
  • react-testing-library: 一个DOM测试库,官方推荐的Jest的第三方库,类似于jQuery,提供更简洁的Api,如定位元素,断言等
    • 思想:区别于enzymereact-testing-library主要从用户角度去写单元测试,比如一个“详情”按钮,功能是点击按钮有弹窗,且发送请求,然后显示页面详细信息;那么我测试的思路点可以是,页面有一个按钮、文字为“详情”、点击后,页面会有个弹窗、弹窗内容显示“...”;而不用去关心组件内部代码的实现细节,比如props传值等等
    • 单元测试编写思路,参考ARRANGE-ACT-ASSERT模式
      • Arrange:准备好需要测试的内容,比如render出组件对应的dom元素;比如初始化一些变量值
      • Act:包含组件的功能,可能是用户的交互如userEventclick;或者发送异步请求使用async...await;或者调用某些方法;
      • Assert:断言expect预期的结果。Act后会导致某些预期的反应,比如click后页面元素的变化;异步请求后,数据的变化;方法的调用次数等等;

使用环境

  • 项目如果是使用Create-React-App创建的,则默认已安装
    • @testing-library/jest-dom: "^5.14.1"
    • @testing-library/react: "^11.2.7"
    • @testing-library/user-event: "^12.8.3"
    • 根据项目具体情况可额外添加其它依赖,如:@testing-library/react-hooks
  • 可在package.json中添加jest(与script同级),或者在项目中添加jest.config.ts 文件
    "jest": {
        "verbose": true, // 是否输出 descripe 和 test/it 中的测试描述信息
        "collectCoverage": true, // 是否输出测代码试覆盖率
        "setupFilesAfterEnv": [
          "<rootDir>/src/setupTests.ts" // 在每个测试文件执行之前调用一些默认执行代码的文件路径
        ]
      },
    
    • 下面为verbose属性配置为false、true后,输出信息的区别 1.png
    • 下面为setupTests.ts文件中可配置默认执行代码例子
    import React from 'react'
    import '@testing-library/jest-dom';
    import { renderHook } from "@testing-library/react-hooks";
    
    beforeEach(() => {
      return renderHook(() => {
        ...
      })
    })
    ...
    

知识点:react-testing-library

  • DOM API
    • render:
          function render(
              ui: React.ReactElement<any>,
              options?: {通常比较少用到,包含container、baseElement等可配置选项}
          ):RenderResult
          // RenderResult返回一些元素定位方法:getByText、queryAllByTestId等
          eg:const {getByText, queryAllByTestId} = render(<Component />)
          // 也可以直接调用render,引入screen调用
      
      Example:
      // my-component.test.ts
      import {render} from '@testing-library/react'
      import userEvent from '@testing-library/user-event'
      import '@testing-library/jest-dom'
      import MyComponent from './my-component'
      
      // 使用render方法渲染组件或者dom,使用定位方法如 getByText 定位元素,然后断言
      test('test Hello World', ()=> {
          const {container, getByText} = render(<MyComponent/>) // Arrange
          expect(getByText('Hello World').toBeInTheDocument()
      })
      
    • cleanup:
      • Unmounts React trees that were mounted with render
      • 防止内存泄漏 Example:
      import {render, cleanup} from '@testing-library/react'
      // 结合钩子函数使用,在每次调用测试前清楚之前渲染的dom
      afterEach(cleanup) 
      
    • dom定位方法
      • getBy:定位页面已存在dom元素,如果不存在则抛出异常
        -getByText: find by element text content
        -getByRole: find by aria role
        -getByLabelText: find by label or aria-label text content
        -getByPlaceholderText: find by input placeholder value
        -getByAltText: find by img alt attribute
        -getByTitle: find by title attribute or svg title tag
        -getByDisplayValue: find by form element current value
        -getByTestId: find by data-testid attribute

      • queryBy: 定位页面不存在的dom元素,如果不存在,返回null,不抛出异常(具体方法同上)

      • fintBy: 定位页面当中的异步元素,如果不存在,抛出异常(具体方法同上)

      • 下面可以看到三大类方法区别:

        2.png

  • 异步方法结合async...await使用
    • waitFor(Promise):会一直等到内部函数执行完毕或者抛出异常为止
    • waitForElementToBeRemoved(Promise):直到内部函数不返回Dom元素为止
  • userEvent
    • click(element):单击
    • dbClick:双击
    • async type(element, text, [options]):输入文本
    • selectOptions(element, values):表单选择
    • tab({shift, focusTrap}):模拟 tab 键(切换 focus)
  • 补充Jest快照测试snapshot
       import React from 'react';
       import renderer from 'react-test-renderer';
       import Link from '../Link.react';
    
       it('renders correctly', () => {
           const tree = renderer
           .create(<Link page="http://www.facebook.com">Facebook</Link>)
           .toJSON()
       
           expect(tree).toMatchSnapshot()
       })
    

总结

学习单元测试,结合ARRANGE-ACT-ASSERT这个思路,根据官方文档掌握对应每一步要用的api,按照用户的角度去写单元测试就很简单了,通常就是以下三步:

  • Arrange:元素定位结合renderjest-dom——getby/queryby/findby...jest.mock()...等等
  • Act:userEventwaitForasync...await...等等
  • Assert: 结合jest-dom-toBeInTheDocumenttoBeChecked等等

参考资料

Jest 官方文档
testing-library 官方文档
ARRANGE-ACT-ASSERT