发布于 2026-01-06 8 阅读
0

设置 Jest + React-Testing-Library TypeScript 配置

配置 Jest + React 测试库

TypeScript 配置

在过去的几个月里,我非常荣幸地使用了我在 React + Friends 环境中最喜欢的库——react-testing-library。这个库简直是一件艺术品。

目前还没有哪个库比它更直观、更易于设置且更适合新手。在某些情况下,它甚至可以帮助开发者快速融入新项目。本指南旨在分享我在为不同项目配置工具的过程中积累的经验,以及我个人采用的最佳实践。我们将逐步讲解如何使用 create-react-app 设置环境,以及如何从零开始搭建一个 React 项目(包括 jest*、webpack、babel 等)。
与其说这是一份测试指南,不如说它是一份关于如何在 React 应用中搭建测试环境的分步教程。关于如何编写单元测试,有很多比我更有经验的人撰写过相关指南。但是,我认为提高测试能力的最佳方法就是实际编写测试。我发现,无论是新手还是经验丰富的开发者,在编写测试时都会遇到一个很大的难题:如何区分 Jest 和 react-testing 库的作用。以我自己的经验来看,要学会区分这一点,就要不断重复、遇到困难、进行研究、反复尝试。

非常期待收到社区的反馈!

指数:

  • 我总结的目录文件结构和约定
  • Jest + RTL 和 create-react-app 入门
  • 从零开始学习 Jest + RTL
  • 使用 TypeScript 进行配置
  • 示例
  • 精选资源,助您轻松入门 RTL 教学

文件结构:

我写了一篇关于如何构建我的 React 应用(不使用 hooks)的文章:
https://blog.usejournal.com/how-i-structure-my-react-apps-86e897054593

长期以来,最佳实践和惯例都是创建一个文件夹,__ test __然后将测试文件放在该文件夹内,甚至在 React 出现之前就是如此。我个人的做法略有不同,这纯粹是个人偏好。随着我不断使用这套系统,它逐渐成型,我和我的团队都很喜欢这种方式(我想是这样!)。

我的项目中典型的文件结构如下:

- node_modules
- public
- src
  - components
    - MyComponent
      - MyComponent.jsx
      - MyComponent.styles.js
      - MyComponent.test.js      // here is what I do different
    - index.js            // source of truth for component export
  - utils
    - helpers.js
  - pages
  - App.jsx
  - App.test.jsx
  - App.styles.js
  - index.js
Enter fullscreen mode Exit fullscreen mode

正如我在上面的评论中提到的,这是我个人与流行做法最大的不同之处。在我看来,在组件驱动开发的时代,为组件创建这种封装的环境更有意义(最重要的是保持一致性,并选择让你感到舒适的方式😁)。为每个组件添加一个测试文件夹,在大型代码库中,由于组件和组件变体众多,这似乎不符合 DRY 原则。此外,我个人认为添加这个文件夹没有任何好处。而且,当 Jest 遍历根目录并查找要运行的文件时,它并不会专门查找某个文件夹(当然,这取决于 Jest 的正则表达式模式)。

命名和大小写规则:

- PascalCase for component file name and folder name
- Generally, I want to indicate if my components are container or component.
  Containers will usually be class components that contain state and logic,
  whereas components will house the actual content, styling and receive props from the container. 
  Example:
  - `MyComponent.container.js`
  - `MyComponent.component.js`
  - `MyComponent.jsx`          // if no container
  - `MyComponent.styles.js`
- lowerCamelCase for Higher Order Component file and folder name
- lowercase for all other root directory folders. For example: `src`, `components`, `assets`
Enter fullscreen mode Exit fullscreen mode

一些值得注意的惯例

描述方法:

describe('My component', () => {
  // group of test()
})
Enter fullscreen mode Exit fullscreen mode

describe 方法是 Jest 所谓的全局方法之一,无需导入或引入即可使用。describe 语句尤其用于将类似的测试用例分组在一起。

测试方法

test('some useful message', () => {
   // logic
}, timeout) // timeout is optional
Enter fullscreen mode Exit fullscreen mode

测试函数才是核心所在。正是这个函数实际运行你的测试。根据 Jest 的文档,第一个参数是测试名称,第二个参数是回调函数,你可以在其中添加测试逻辑(断言等),第三个参数(可选)是超时时间。

测试函数还有一个别名,可以与 it() 互换使用:it('test', () => {})


Jest 和 RTL 与 CRA 的入门指南:

坦白说,我喜欢用 CRA,它能帮你搞定一切,还能减少随着依赖版本更新而产生的额外技术开销。用 react-scripts,你基本上只需要操心这部分就行了。

npx create-react-app ProjectName

npx create-react-app ProjectName --typescript

首先,我做的第一件事就是安装所需的依赖项:

npm install --save-dev @testing-library/jest-dom

npm install --save-dev @testing-library/react

我在package.json文件中添加了以下脚本:

"test": "jest -c jest.config.js --watch"
Enter fullscreen mode Exit fullscreen mode

简要说明:当我开始一个新的 React 项目时,第一件事就是添加这些依赖项,styled-components以及types在需要时添加我的其他依赖项。

测试库文档将 jest-dom 定义为 React 测试库的配套库,它为 Jest 提供自定义 DOM 元素匹配器。本质上,它是一个依赖项,提供了诸如 `{{ DOM 元素匹配器 }}` 或 `{{ DOM 元素匹配器 }}` 之类的语句(或匹配器* toHaveStylestoHaveAttribute

示例:
expect(Component).toBeInTheDocument()<- 匹配器

项目创建完成后,我在 src 文件夹中添加了一个名为 . 的文件setupTests.js

- src
  - components
  - App.js
  - setupTests.js
Enter fullscreen mode Exit fullscreen mode

setupFiles操作在测试框架安装到环境中之前执行。对于我们的情况来说,这一点尤为重要,因为它允许我们在执行测试之前运行正确的导入语句。这使我们有机会添加一些额外的导入语句。

所以,在你的 setupTests.js 文件中:

import '@testing-library/jest-dom/extend-expect'
Enter fullscreen mode Exit fullscreen mode

那个文件就到此为止了 :)。

这就是您开始使用和运行所需的一切jestreact-testing-library


从零开始使用 Jest 和 RTL 构建 React 应用:

这部分会稍微长一些,因为需要介绍和配置的工具更多。我们会一步一步地讲解如何从零开始构建一个 React 应用。Babelcreate-react-app已经抽象了很多配置的复杂性,而且做得非常好。现在我们需要配置 Babel,而对于我们的情况来说,最重要的是配置 Jest。概括来说,Jest 配置负责确保 Jestjest知道在哪里查找、查找什么以及如何执行。

一个从零开始搭建 React 应用的绝佳资源:
https://blog.bitsrc.io/setting-a-react-project-from-scratch-using-babel-and-webpack-5f26a525535d

目录结构

- node_modules`
- public
  - index.html
- src
  - components
    - MyComponent
      - MyComponent.jsx
      - MyComponent.styles.js
      - MyComponent.test.js      // here is what I do different
    - index.js             // source of truth for component export
  - utils
  - pages
  - App.jsx
  - App.test.jsx
  - App.styles.js
  - store.js
  - index.js
- webpack.config.js
- jest.config.js
- .gitignore
- .eslintrc
- .prettierrc
Enter fullscreen mode Exit fullscreen mode

index.html:

<!DOCTYPE html>
  <html lang="en">    
    <head>        
      <meta charset="UTF-8" />        
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />        
      <meta http-equiv="X-UA-Compatible" content="ie=edge" />                                    
      <title>React JS + Webpack</title>
    </head>    
    <body>        
      <div id="root"></div>    
    </body>
  </html>
Enter fullscreen mode Exit fullscreen mode

App.js

import React from 'react';

const App = () => <h1>Hi World</h1>;

export default App;
Enter fullscreen mode Exit fullscreen mode

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

webpack.config.js:

const webpack = require("webpack");

// plugins
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "./main.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ["file-loader"]
      },
      { test: /\.jsx$/, loader: "babel-loader", exclude: /node_modules/ },
      { test: /\.css$/, use: ["style-loader", "css-loader"] }
    ]
  },
  devServer: {
    contentBase: "./dist",
    hot: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      filename: "./index.html"
    }),
    new webpack.HotModuleReplacementPlugin()
  ]
};
Enter fullscreen mode Exit fullscreen mode

jest.config.js:

module.export = {
  roots: ['<rootDir>/src'],
  transform: {
    '\\.(js|jsx)?$': 'babel-jest',
  },
  testMatch: ['<rootDir>/src/**/>(*.)test.{js, jsx}'], // finds test
  moduleFileExtensions: ['js', 'jsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/public/'],
  setupFilesAfterEnv: [
    '@testing-library/jest-dom/extend-expect'', 
    '@testing-library/react/cleanup-after-each'
  ] // setupFiles before the tests are ran
};
Enter fullscreen mode Exit fullscreen mode

MyComponent.js:

import React from 'react'
import styled from 'styled-components'

const MyComponent = props => {

  return (
    <h1>`Hi ${props.firstName + ' ' + props.lastName}!`</h1>
  )
}
export default MyComponent

Enter fullscreen mode Exit fullscreen mode

MyComponent.test.js:

import React from 'react'
import { render, cleanup } from '@testing-library/react'
import MyComponent from './MyComponent'

afterEach(cleanup)

describe('This will test MyComponent', () => {
  test('renders message', () => {
     const { getByText }= render(<Mycomponent 
                                 firstName="Alejandro"
                                 lastName="Roman"
                              />)

     // as suggested by Giorgio Polvara a more idiomatic way:
     expect(getByText('Hi Alejandro Roman')).toBeInTheDocument()
})
Enter fullscreen mode Exit fullscreen mode

输入示例:

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Input from './Input'


test('accepts string', () => {
  // I try to avoid using data-testid as that is not something a user would
  // use to interact with an element. There are a lot of great query and get 
  // methods
  const { getByPlaceholderText } = render(<Input placeholder="Enter
                                           Text" />);
  const inputNode = getByPlaceholderText('Search for a problem or application name');

  expect(inputNode.value).toMatch('') //tests input value is empty

  // if you need to perform an event such as inputing text or clicking
  // you can use fireEvent
  fireEvent.change(inputNode, { target: { value: 'Some text' } }));

  expect(inputNode.value).toMatch('Some text'); // test value 
                                                // is entered
});
Enter fullscreen mode Exit fullscreen mode

TypeScript 配置

tsconfig.json:

{
  "include": [
    "./src/*"
  ],
  "compilerOptions": {
    "lib": [
      "dom",
      "es2015"
    ],
  "jsx": "preserve",
  "target": "es5",
  "allowJs": true,
  "skipLibCheck": true,
  "esModuleInterop": true,
  "allowSyntheticDefaultImports": true,
  "strict": true,
  "forceConsistentCasingInFileNames": true,
  "module": "esnext",
  "moduleResolution": "node",
  "resolveJsonModule": true,
  "isolatedModules": true,
  "noEmit": true
  },
  "include": ["./src/**/*"],    
  "exclude": ["./node_modules", "./public", "./dist", "./.vscode"]
}
Enter fullscreen mode Exit fullscreen mode

jest 配置:

module.exports = {
  roots: ['<rootDir>/src'],
  transform: {
    '\\.(ts|tsx)?$': 'babel-jest',
  },
  testMatch: ['<rootDir>/src/**/?(*.)test.{ts,tsx}'],   // looks for your test
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testPathIgnorePatterns: ['/node_modules/', '/public/'],
  setupFilesAfterEnv: [
    'jest-dom/extend-expect',
    '@testing-library/react/cleanup-after-each'
  ]  // sets ut test files
};
Enter fullscreen mode Exit fullscreen mode

webpack 配置:

const path = require('path')

// Plugins
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: {
        dev: './src/index.tsx',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].bundle.js',
    },
    devServer: {
        compress: true,
        port: 3000,
        hot: true,
    },
    devtool: 'source-map',
    resolve: {
        extensions: ['.ts', '.tsx', '.js', '.jsx'],
    },
    module: {
        rules: [
            /**
             * Gets all .ts, .tsx, or .js files and runs them through eslint
             * and then transpiles them via babel.
             */
            {
                test: /(\.js$|\.tsx?$)/,
                exclude: /(node_modules|bower_components)/,
                use: ['babel-loader'],
            },

            /**
             * All output '.js' files will have any sourcemaps re-processed by
             * source-map-loader.
             */
            { test: /\.js$/, enforce: 'pre', loader: 'source-map-loader' },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
        }),
    ],
}
Enter fullscreen mode Exit fullscreen mode

额外资源:

以下是一些帮助我学习 React 测试库不同方面的资源:

文档:

https://testing-library.com/docs/react-testing-library/intro

Create-react-app:https://www.youtube.com/watch ?v=Yx-p3irizCQ&t=266s

Redux 测试:https://www.youtube.com/watch?v=h7ukDItVN_o&t= 375s

组件单元测试:https://www.youtube.com/watch?v=KzeqeI046m0 &t=330s

模拟和更多组件测试:https://www.youtube.com/watch?v= XDkSaCgR8g4&t=580s

传送门:https://www.youtube.com/watch?v=aejwiTIBXWI&t =1s

嘲讽:https://www.youtube.com/watch?v=9Yrd4aZkse8 &t=567s

测试异步组件:https://www.youtube.com/watch?v= uo0psyTxgQM&t=915s

文章来源:https://dev.to/aromanarguello/getting-started-with-jest-react-testing-library-4nga