React 组件单测最佳实践 Vitest + react-testing-library

React组件单测最佳实践

单元测试

单元测试简称单测。我们采用 Vitest + react-testing-library (RTL) 做为测试框架。目录结构如下:

tests

setup.mjs # 测试初始化脚本

src

ComponentA

index.tsx

index.test.tsx # 建议和源文件放一起,好处是让新加入的成员能快速感知到 TDD 意识

...

环境准备

安装依赖:

npm install vitest jsdom @testing-library/react @testing-library/jest-dom --save-dev

新增文件 tests/setup.mjs,写入以下内容:

// tests/setup.js

// @ts-check

import { expect, afterEach } from 'vitest';

import { cleanup } from '@testing-library/react';

import * as matchers from "@testing-library/jest-dom/matchers";


expect.extend(matchers); // 对 expect 的能力增强,不用可注释


// 清屏:解决单个文件内多个 test 多次 render,后面的 render 会累积前面 render 产生的 DOM 节点问题

afterEach(() => {

cleanup();

});


// 如果遇到 window.matchMedia undefined is not a function 可以开启

// window.matchMedia = vi.fn().mockImplementation((query) => ({

// matches: false,

// media: query,

// onchange: null,

// addListener: vi.fn(),

// removeListener: vi.fn(),

// }))

新增 vitest.config.mjs 写入以下内容:

// @ts-check

import { defineConfig } from 'vitest/config'

import react from '@vitejs/plugin-react'


export default defineConfig({

// @ts-expect-error

plugins: [react()],

test: {

setupFiles: './tests/setup.mjs',

environment: 'jsdom',

coverage: {

thresholds: {

branches: 20, // 自行设置合理值

functions: 20,

lines: 20,

},

include: ['src/'], // 只计算 src 内文件覆盖率

},

},


resolve: {

alias: [

{

find: '@',

replacement: '/src', // 如果有设置 tsconfig.json paths 比如 `@`

},

{

find: 'name-in-package.json', // 组件名,package.json 的 name。目的是文档测试

replacement: '/src',

},

],

},

})

更新 package.json,新增以下 script:

"test": "vitest",

"ci": "vitest run --coverage",
  • test:本地写单测会用到,将 watch 单测和配置文件达到热更新的效果。
  • ci:ci 流程会用到或在发布前进行自动化测试,此处会读取 vitest.config.mjs 中设置的 coverage 阈值,如果低于阈值 ci 将失败。
书写单测

下面结合例子来说明如何写一个单测。

比如有如下组件,我们想测试是否能正常展示 Hello React

// src/App.tsx

import * as React from 'react';


const title = 'Hello React';


function App() {

return <div>{title}</div>;

}


export default App;

新增测试文件 src/App.test.tsx

import * as React from 'react';

import { render, screen } from '@testing-library/react';


import App from './App';


describe('App', () => {

it('renders App component', () => {

render(<App />);


expect(screen.getByText('Hello React')).toBeDefined();

});

});

再比如我们有一个登录组件,我们想测试在没有输入任何内容时,点击登录按钮会出现提示。

expect(screen.queryByText('Please Enter Username / Email.')).toBeNull()

expect(screen.queryByText('Please Enter Password.')).toBeNull()


fireEvent.click(screen.getByRole('button'))


expect(screen.queryByText('Please Enter Username / Email.')).toBeDefined()

expect(screen.queryByText('Please Enter Password.')).toBeDefined()

注意我们此处用的是 queryByText,因为 getByText 找不到文本将报错。

更多示例以及关于 get|queryByText 等 API 使用和选择可以参考 www.robinwieruch.de/react-testi…。 此处不再详述。

文档测试

如果我们能对 demo 进行单测,那该多好。相当于对用户契约有了自动化保障,类比 Rust 的 documentation test。

Nothing is better than documentation with examples. But nothing is worse than examples that don't work because the code has changed since the documentation was written.

来自 doc.rust-lang.org/book/ch14-0…

“没有什么比带有示例的文档更好了。但是,没有什么比示例不起作用更糟糕了,因为自从编写文档以来,代码已经发生了变化。”

文档中的示例可以帮助理解,但需要确保示例的代码与文档一致,传统做法是定期检查代码与文档的匹配度。我们是否还有更好的手段?那就是文档测试

针对 dumi 而言,文档测试是指我们在 markdown 中写的示例。还记得我们在 vitest.config.mjs 中配置的 alias 吗?

{

find: 'name-in-package.json', // 组件名,package.json 的 name。目的是文档测试

replacement: '/src',

},

该配置是让示例代码中 import { ComponentA } from 'name-in-package.json' 能正确解析的关键。 举例说明,若我们有如下文档:

// index.md

<code src="./demo/app.tsx"></code>

demo 内容为:

// demo/app.tsx

import React from 'react'

import { ComponentA } from 'name-in-package.json'


export default () => {

return <ComponentA ... />

}

在同目录 demo/ 下新增测试文件 demo/app.test.tsx:

// demo/app.test.tsx

import * as React from 'react';

import { render, screen } from '@testing-library/react';


import Demo from './demo';


describe('Demo', () => {

it('renders Demo component', () => {

render(<Demo />);


expect(screen.getByText('Hello React')).toBeDefined();

});

});

至此我们已完成对一个组件的单测文档测试,可以在该组件的 index.md 标题添加单测通过的 tag!

index.md:

- # ComponentA / 中文标题

+ # ComponentA / 中文标题 <Badge type="success">test passing</Badge>

当然也可以增加类似 npm 的 badge:

[![dumi](https://img.shields.io/badge/test%20-passing-green)](https://github.com/umijs/dumi)

AI写代码

效果:

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

​​​软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值