Next.js应用Jest + React Testing Library测试框架(上)

124 阅读4分钟

Next.js应用Jest + React Testing Library测试框架(下)

注意事项

  1. 为了便于管理,先在根目录的__tests__下创建打算测试的文件相同路径的测试文件,比如打算测src/a/b/c.ts,则创建__tests__/src/a/b/c.test.ts,再进行测试,可以右键文件夹并复制相对路径,然后在__tests__下新建文件夹,粘贴即可
  2. 测试文件名的命名为[被测试文件的名称]_[导出内容].test.tsx(注意这里如果测试内容不包含tsx代码,写ts也是可以的)的格式,如Image组件文件名为index.tsx,只导出了default,则命名为index.test.tsx。如a.ts,导出了getUserInfo和logout,则分别命名为a_getUserInfo.test.ts和a_logout.test.ts
  3. jest的test函数有test和it两个名称,统一使用it
  4. 使用userEvent时,应在render之前调用const user = userEvent.setup();,然后使用user来调用点击等交互方法。

常见错误

  1. 错误:通过beforeAll、beforeEach、afterAll、afterEach来复用代码,当测试文件越来越大时,各个测试用例就会严重耦合,这会导致可读性可维护性急剧下降,我们需要从头读到尾来跟踪一个变量的变化。
    解决办法:通过函数来复用代码,适当允许代码冗余,多处复用时再进行抽离。有兴趣的同学可以看看这篇avoid-nesting-when-youre-testing
    注意:这里不是说禁止使用before*和after*,初始化服务器等不会影响测试用例中变量的值变化的内容都可以放进去
  2. 错误:使用describe包裹一组代码,只有当文件越来越大时,我们才会需要通过describe对代码进行分组,但是既然文件都大了,为什么我们不一开始就通过拆分文件的方式来分割代码呢?
    解决办法:通过文件来对代码进行分割,共享的函数可以放在__tests__/helpers下

入门

由于jest官方文档纯英文,且中文翻译不行,所以推荐大家看这个入门
前端测试之Jest深入浅出
React Testing Library也是没有中文文档
React Testing Library使用总结

新项目

使用如下命令创建项目即可

npx create-next-app@latest --example with-jest with-jest-app

老项目

使用如下命令安装依赖

npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest jest-transformer-svg
# or
yarn add -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest jest-transformer-svg
# or
pnpm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest jest-transformer-svg

新建jest.config.ts并粘贴如下内容

/**
 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

import type { Config } from "jest";
import nextJest from 'next/jest.js'

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
})

const config: Config = {
  // 每次测试前自动清除mock calls, instances, contexts和results
  clearMocks: true,
  // 指示应该使用哪个提供程序来对代码进行覆盖测试。允许的值为“babel”(默认值)或“v8”。
  coverageProvider: "v8",
  // 一个包含文件路径模式的数组,用于指定需要收集覆盖信息的一组文件。如果某个文件匹配指定的路径模式,即使没有针对该文件的测试,并且在测试套件中从未要求收集其覆盖信息,也会收集其覆盖信息。
  collectCoverageFrom: [
    '<rootDir>/packages/**/*.{js,jsx,ts,tsx}',
    '<rootDir>/src/**/*.{js,jsx,ts,tsx}',
    '<rootDir>/public/**/*.{js,jsx,ts,tsx}',
    '!<rootDir>/src/api/**',
    '!**/*.d.ts',
    '!**/node_modules/**',
    '!<rootDir>/out/**',
    '!<rootDir>/.next/**',
    '!<rootDir>/*.config.js',
    '!<rootDir>/coverage/**',
  ],
  // web应用使用jsdom node应用使用node
  testEnvironment: "jsdom",
  // 测试位于 /__tests__/ 目录下的任何 .js, .jsx, .ts, 或 .tsx 文件。
  testRegex: "/__tests__/.*\\.(jsx?|tsx?)$",
  // 不测试的文件
  testPathIgnorePatterns: ['/__tests__/helpers/.*'],
  // 如果引用文件没有指定扩展名,会从左往右尝试加上如下后缀匹配
  moduleFileExtensions: ["tsx", "ts", "js", "jsx", "json", "node"],
  // 配置 Jest 的 setup 文件位置,Jest 的 setup 文件用于在 执行测试时先执行一些固定的初始化操作
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // 处理路径别名 https://kulshekhar.github.io/ts-jest/docs/getting-started/paths-mapping/
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
    "^@packages/(.*)$": "<rootDir>/packages/$1",
  },
  transform: {
    // 处理SVG
    '^.+\\.(svg)$': 'jest-transformer-svg'
  }
};

// 处理SVG
export default async (...args: any[]) => {
  const fn = createJestConfig(config);
  // @ts-ignore
  const res = await fn(...args);
  delete res.moduleNameMapper!['^.+\\.(svg)$'];
  return res;
};

在根目录下新建jest.setup.ts,用于在执行测试时先执行一些固定的初始化操作,引入jest-dom用于扩展matchers,详情见testing-library/jest-dom

import '@testing-library/jest-dom';

package.json中增加如下命令

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
}

test: 跑一次所有测试代码
test:watch: 只要文件发生改动就会跑测试
test:coverage: 获取覆盖率报告
小技巧: 运行npm test -- regExpOfFilePath即可测试单个文件,regExpOfFilePath是能匹配文件名的正则表达式

在tsconfig.json增加如下配置

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"],
      "@packages/*": ["./packages/*"],
    },
    "types": [
      "jest"
    ]
  },
  "include": ["__tests__/**/*.ts", "__tests__/**/*.tsx", "./jest-setup.ts"],
}

测试

  1. 写一个Home组件
    export default function Home() {
      return <h1>Home</h1>
    }
    
  2. 在根目录下新建__tests__文件夹
  3. 在__tests__创建测试文件,注意以tsx结尾,并粘贴如下内容
    import { render, screen } from '@testing-library/react'
    import Home from '../pages/index'
    
    it('renders a heading', () => {
      render(<Home />)
    
      const heading = screen.getByRole('heading', { level: 1 })
      expect(heading).toBeInTheDocument()
    })
    
  4. 在命令行跑npm run test
  5. 如果出现如下内容说明完成了所有的配置
    > jest
    
    PASS  __tests__/index.test.tsx
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   1 passed, 1 total
    Time:        2.845 s
    Ran all test suites.
    

小技巧

  1. 当异步函数如user.click(button)超时,且没有具体报错信息时,可以使用waitFor进行包裹,如await waitFor(() => user.click(button));,当waitFor超时(默认1000ms),默认的onTimeout函数会把超时原因打印出来。

参考资料

Setting up Jest with Next.js
testing-library/jest-dom
avoid-nesting-when-youre-testing
使用 React Testing Library 的 15 个常见错误