(2.2w字)前端单元测试之Jest详解篇

Jest

Jest 概述

Jest是一个领先的JavaScript测试框架,特别适用于React和Node.js环境。由Facebook开发,它以简单的配置、高效的性能和易用性而闻名。Jest支持多种类型的测试,包括单元测试、集成测试和快照测试,后者用于捕获组件或数据结构的状态,以便于后续的比较和验证。Jest自动化模拟依赖项和异步代码测试,提高了测试的可靠性和灵活性。其并行测试执行机制显著加快了测试过程,而交互式监视模式则在开发过程中提供即时反馈。此外,Jest还提供内置的代码覆盖率工具,帮助开发者优化测试范围。因其强大的功能和广泛的社区支持,Jest成为现代JavaScript项目中不可或缺的测试工具。

Jest 环境配置

安装包

1、jest:这是 Jest 测试框架本身。

2、@types/jest:这是 Jest 的 TypeScript 类型定义,用于在使用 TypeScript 编写测试时提供类型检查和自动完成功能。

3、babel-jest:这是用于将 Jest 集成到使用 Babel 的项目中的插件。它允许 Jest 处理通过 Babel 转换的代码。

4、ts-jest:这是一个 Jest 转换器,用于处理 TypeScript 文件。它基本上允许 Jest 理解和运行 TypeScript 测试代码。

5、jest-transform-stub:这个插件用于处理非 JavaScript 资源(如 CSS 和图片)的导入,这在 Jest 测试中通常会被忽略或需要特殊处理。

bash 复制代码
npm install --save-dev jest @types/jest babel-jest ts-jest jest-transform-stub

6、@testing-library/jest-dom:提供一套针对 DOM 元素的 Jest 断言,非常适用于在测试 React 组件时使用。

7、@testing-library/react:用于测试 React 组件,它提供了渲染组件、查询 DOM 元素以及与组件交互的工具。

8、@testing-library/user-event:这个库用于模拟用户事件(如点击、输入等),可用于更逼真地测试用户交互。

bash 复制代码
npm install --save-dev @testing-library/jest-dom @testing-library/react @testing-library/user-event

9、eslint-plugin-jest:这是一个 ESLint 插件,提供针对 Jest 测试的特定规则,有助于保持测试代码的质量和一致性。

10、react-test-renderer:

这是一个用于渲染 React 组件为 JavaScript 对象的库,常用于 Jest 的快照测试。它可以在不需要 DOM 环境的情况下测试 React 组件的输出,这对于在 Node 环境下运行的 Jest 测试非常有用。

bash 复制代码
npm install --save-dev eslint-plugin-jest react-test-renderer

package.json

1、基本的运行测试用例配置,npm test即可运行

--watchAll:这个参数告诉 Jest 进入 "watch" 模式。在这个模式下, Jest 会监视项目中的文件变化。当修改并保存了代码文件(包括测试文件和被测试的源代码文件)时, Jest 会自动重新运行相关的测试。

--watchAll--watch 不同之处在于,--watchAll 会在初次运行时执行所有测试,而 --watch 只在检测到文件更改时运行相关测试。

json 复制代码
"test": "jest --watchAll",

2、运行某个文件夹下的所有测试文件,src/tests代表文件夹路径

json 复制代码
"test:folder": "jest --watchAll --testPathPattern=src/tests",

3、单独运行某个测试文件,src/renderer/login/loginApi.test.tsx代表需要测试的文件路径

json 复制代码
"test:single": "jest --watchAll jest --findRelatedTests src/renderer/login/loginApi.test.tsx",
常见的 Jest 命令行操作

1、f 只会跑测试未通过的用例,再次点击 f 会取消当前模式。

2、o 只监听已改变的文件,如果存在多个测试文件,可以开启,会与当前 git 仓库中的提交进行比较,需要使用 git 来监听哪个文件修改了,也可以将 --watchAll 改为 --watch 只会运行修改的文件。

3、a 运行所有测试,如果在 watch 模式中使用了 f 或 o ,使用 a 可以恢复运行所有测试。

4、u 用于更新 Jest 快照测试中的快照。如果更改了渲染组件的输出,可以使用此命令更新快照。

5、w 显示 Jest watch 模式中的所有可用命令和选项的列表。

6、q 退出 Jest 的 watch 模式。

7、i 只会运行之前运行失败的测试文件,但提供更交互式的体验。

.babelrc

当使用 Jest 测试一个使用 Babel 编译的项目时,Jest 会通过这些配置来正确处理和理解 JavaScript 代码。

json 复制代码
{
	// 设置插件集合
	"presets": [
		// 使用当前插件,可以进行转换
		// 数组的第二项为插件的配置项
		[
			"@babel/preset-env",
			{
				// 根据 node 的版本号来结合插件对代码进行转换
				"targets": {
					"node": "current"
				}
			}
		]
	]
}

setupTests.js

该文件设置测试环境中的全局对象和模拟(mock)某些模块,在本项目中针对 Electron 和 Node.js 的相关模块进行模拟,以便在不依赖实际 Electron 或浏览器环境的情况下测试特定的功能。

js 复制代码
/* eslint-disable no-undef */
const electronMock = require('./__Mock__/electronMock')

global.window.require = jest.fn(moduleName => {
	if (moduleName === 'electron') {
		return electronMock
	}
	if (moduleName === '@electron/remote') {
		return {
			require: jest.fn(module => {
				// 模拟 Node.js 模块,如 fs
				if (module === 'fs') {
					return {} // 返回 fs 的模拟实现
				}
				// 其他模块模拟...
			}),
		}
	}
})
global.window.matchMedia =
	global.window.matchMedia ||
	function () {
		return {
			matches: false,
			addListener: function () {},
			removeListener: function () {},
		}
	}

jest.config.ts

jest.config.ts 是一个使用 TypeScript 编写的 Jest 配置文件。可以使用npx jest --init初始化命令来生成一个基本的配置文件。

ts 复制代码
export default {
    // 自动清除 mock 调用和实例
    clearMocks: true,
    // 开启代码覆盖率收集
    collectCoverage: true,
    // 代码测试覆盖率通过分析那些文件生成的,!代表不要分析
    collectCoverageFrom: ['**/*.{ts,js,tsx}', '!**/node_modules/**', '!**/vendor/**'],
    // 代码覆盖率报告的输出目录
    coverageDirectory: 'coverage',
    // 代码覆盖率的收集器,这里使用 V8 引擎
    coverageProvider: 'v8',
    // 代码覆盖率报告的格式
    coverageReporters: [
        'text-summary',
        'lcov',
    ],
    globals: {
        'ts-jest': {
            // 关闭 ts-jest 的诊断信息
            diagnostics: false,
        },
    },
    // 引入模块时,进行自动查找模块类型,逐个匹配
    moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
    // 模块名字使用哪种工具进行映射
    moduleNameMapper: {
        '^@/(.*)$': '<rootDir>/src/$1', //将 @/ 映射到 src/ 目录
        '\\.(css|less)$': 'jest-transform-stub',
        '^localTypes$': '<rootDir>/src/types.ts',
        '^localUtils$': '<rootDir>/src/utils/index.ts',
        '^localConst$': '<rootDir>/src/utils/constants.ts',
        '^Assets/(.*)$': '<rootDir>/assets/$1',
    },
    preset: 'ts-jest',
    rootDir: undefined,
    // 检测从哪个目录开始,rootDir 代表根目录
    roots: ['<rootDir>/src'],
    // 在运行测试之前执行的文件(设置测试环境)
    setupFilesAfterEnv: ['./setupTests.js'],
    // 测试运行的环境,会模拟 dom
    testEnvironment: 'jsdom',
    // 哪些文件会被认为测试文件
    testMatch: [
        // src 下的所有 __tests__ 文件夹中的所有的 js jsx ts tsx 后缀的文件都会被认为是测试文件
        '<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
        // scr 下的所有以 .test/spec.js/jsx/ts/tsx 后缀的文件都会被认为是测试文件
        '<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
    ],
    // 测试时忽略的路径
    testPathIgnorePatterns: ['\\\\node_modules\\\\'],
    // 测试文件中引用一下后缀结尾的文件会使用对应的处理方式
    transform: {
        '^.+\\.(t|j)s$': 'ts-jest',
        '\\.svg$': '<rootDir>/__Mock__/svgTransform.js',
    },
}

__Mock__文件夹

文件夹用于存放模拟(mock)模块

自动模拟:当调用jest.mock('moduleName')时,Jest会查找__mocks__文件夹中名为 moduleName 的文件,并自动使用该文件中的模拟实现。这意味着不需要在每个测试文件中手动设置模拟。

第三方模块模拟:这个机制不仅适用于自定义模块,也适用于第三方模块。例如正在使用一个发送 fetch 请求的库,可以在 __mocks__文件夹中创建一个模拟,以避免在测试中发出真实的网络请求。

Mock

Mock fetch 或其他 HTTP 请求库的调用

待补充

Mock 函数

typescript 复制代码
jest.fn()

Mock 第三方模块

待补充

全局函数 describe 和 it

describe 用于将测试分组,而 it 用于定义单个具体的测试用例。可以在 describe 块中放置多个 it 测试用例,也可以嵌套其他 describe 块以创建更详细的测试结构。

typescript 复制代码
// 用于创建一个测试套件,将一组功能或逻辑相关的测试用例组织在一起
describe('测试输入框的校验规则', () => {
    // it 的第一个参数是一个字符串,描述了测试用例应该做什么,有助于代码的可读性和测试结果的理解
    it('输入正常', async () => {
        // ...
    });
    it('必填', async () => {
        // ...
    });
    it('仅支持汉字、字母、数字和-_%.', async () => {
        // ...
    })
    it('以数字、字母或汉字开头', async () => {
        // ...
    })
    it('限长', async () => {
        // ...
    })
});

断言 expect

用于验证代码的行为是否符合预期。 expect 函数接受一个参数---------想要测试的值。然后,expect 返回一个"期望对象",这个对象提供了一系列"匹配器"(matcher)方法,用于声明对这个值的期望。

typescript 复制代码
describe('测试输入框的校验规则', () => {
    it('必填', async () => {
        // ...
        expect(message).toBeInTheDocument()
    })
    it('仅支持汉字、字母、数字和-_%.', async () => {
        // ...
        expect(message).toBeInTheDocument()
    })
    it('以数字、字母或汉字开头', async () => {
        // ...
        expect(message).toBeInTheDocument()
    })
    it('限长', async () => {
        // ...
        expect(message).toBeInTheDocument()
    })
    it('输入正常', async () => {
        // ...
        await waitFor(() => {
            expect(input.className).toMatch('ant-input-status-success')
        })
    })
})

匹配器

toBe :期待是否与匹配器中的值相等,相当于object.is ===

toMatch :匹配当前字符串中是否含有这个值,支持正则

toContain :用于检查数组或字符串是否包含特定项或子串

toBeInTheDocument :判断某个元素是否在文档中,即是否已被渲染到 DOM 上

toHaveProperty :用于检查对象是否具有特定属性,可以选择性地检查属性值

toEqual :是"相等",不是"相同",相当于==

toBeFalsy 和 toBeTruthy :检查一个值是否为假或真

toBeNull :专门用来检查一个值是否为 null

toBeDefined 和 toBeUndefined :这些断言用于检查变量是否已定义或未定义

toThrow :用于检查函数是否抛出错误

not:用于对断言取反

snapshot 快照

会在当前测试文件位于的文件夹下生成一个__snapshots__文件夹,该文件夹下会生成扩展名为 .snap 文件,文件会保存代码运行的结果(如渲染的组件树、数据结构等)。

toMatchSnapshot 方法:接受一个参数是快照名称,字符串类型。

typescript 复制代码
expect(container).toMatchSnapshot('必填')

一定要是 container ,不能是 screen ,用 screen 不会保存 DOM 结构

优势

自动化比较:Jest 自动比较快照,减少了手动检查输出的需要。

简化复杂结构的测试:对于复杂的对象或大型UI组件,编写传统测试断言可能很困难。快照测试可以轻松捕获整个结构。

文档化变化:快照文件也可以作为代码行为的一种文档,让开发者和审阅者理解代码更改的影响。

快照更新:当代码发生更改,导致快照不再匹配时,可以使用 jest --updateSnapshot 命令或jest -u命令来更新快照。

测试用例覆盖率报告

会在主文件夹下生成一个名为 coverage 的文件夹,打开里面的 html 就可以看到各个文件的覆盖率,通常包含以下几种主要的覆盖率类型:

行覆盖率(Line Coverage):测量有多少行代码被测试用例执行过。如果一行代码在测试中至少被执行一次,那么这一行就被认为是覆盖了的。

函数覆盖率(Function Coverage):测量有多少个函数或方法被测试用例调用过。即使函数内的某些行没有被执行,只要函数被调用,它就被认为是覆盖了的。

分支覆盖率(Branch Coverage):测量代码中的每个if语句、循环、switch语句等的每个分支是否都被执行过。这是检查条件语句的完整性的重要指标。

语句覆盖率(Statement Coverage):测量有多少个独立语句被测试执行过。这与行覆盖率类似,但关注的是语句的执行。

React Testing Library

render

渲染 React 组件到一个虚拟的 DOM 环境中以便进行测试。

render 函数接受一个 React 组件作为参数,并返回一个包含多个属性和方法的对象,例如 container 和 debug 。 container 可以调用各类查询函数在渲染的组件中查找元素, debug 可以打印出 baseElement 的内部HTML,用于调试。

typescript 复制代码
describe('测试输入框的校验规则', () => {
    it('输入正常', async () => {
        const Com = <Index />
        const container = render(Com)
        container.debug()
    })
})

screen

在使用 React Testing Library 进行测试时,通常会先用 render 函数渲染组件,然后用 screen 查询和操作元素。screen 对象可以在测试文件中全局访问,无需在每个测试中单独导入或创建。

typescript 复制代码
describe('测试输入框的校验规则', () => {
    it('输入正常', async () => {
        render(<Index />)
        screen.debug()
    })
})

查询函数

React Testing Library 提供了一系列的查询函数,用于在 Jest 测试中找到 DOM 节点。

getBy...

getByText: 根据文本内容查找元素。

getByLabelText: 根据关联的 <label> 文本查找 <input>, <select>, 或 <textarea> 元素。

getByPlaceholderText: 根据占位符文本查找输入框。

getByAltText: 根据图片的 alt 属性文本查找图片元素。

getByTitle: 根据 title 属性查找元素。

getByRole: 根据 ARIA 角色查找元素。

getByTestId: 根据 data-testid 属性查找元素。

queryBy...

queryBy...函数的行为类似于 getBy... 函数,但当查询的元素不存在时,它们返回 null 而不是抛出错误。这对于断言某个元素不在页面上非常有用。

findBy...

findBy...函数是 getBy... 函数的异步版本。它们返回一个 Promise,适用于等待异步操作完成后元素出现在 DOM 中的情况。

...AllBy..., queryAllBy..., findAllBy...

这些函数的行为类似于 getBy..., queryBy..., 和 findBy...,但用于返回多个匹配的元素。如果没有找到匹配的元素,getAllBy...findAllBy... 会抛出错误,而 queryAllBy... 返回一个空数组。

总结:

getBy... 函数用于当确定元素存在时。如果元素不存在,测试将失败。
queryBy... 函数用于当元素可能不存在,需要处理这种情况时。
findBy... 函数用于处理异步逻辑,当需要等待元素出现时。
...AllBy... 函数用于处理有多个匹配元素的情况。

typescript 复制代码
// findByText参数必须是完整的文本,如果是子字符串,需要加上{exact: false}
// findByText不管前缀是screen还是container都可以成功
describe('测试输入框的校验规则', () => {
	it('仅支持汉字、字母、数字和-_%.', async () => {
		const Com = <Index />
		const container = render(Com)
		const input = await screen.findByRole('textbox')
		await userEvent.type(input, '@')
		const messages = await container.findByText('溶剂名称仅支持汉字、字母、数字和-_%.')
	})
})
describe('测试输入框的校验规则', () => {
	it('仅支持汉字、字母、数字和-_%.', async () => {
		const Com = <Index />
		const container = render(Com)
		const input = await screen.findByRole('textbox')
		await userEvent.type(input, '@')
		const messages = await screen.findByText('仅支持汉字、字母、数字和-_%.', {exact: false})
	})
})

waitFor

用于处理异步操作和元素的异步更新。waitFor 常与异步查询函数(如 findBy...)结合使用,用于处理组件状态更新或数据加载。

typescript 复制代码
describe('测试输入框的校验规则', () => {
    it('输入正常', async () => {
        const container = render(<Index />)
        screen.debug()
        const input = await screen.findByRole('textbox')
        await waitFor(() => {
            expect(screen.getByText('必填', { exact: false })).toBeInTheDocument()
        })
    })
})

fireEvent 和 userEvent

Jest 提供fireEventuserEvent模拟用户操作。

fireEvent:直接同步触发 DOM 事件。当调用 fireEvent 的任何方法时(如 fireEvent.click),它会立即生成对应的 DOM 事件,并同步地传递给目标元素。因此,fireEvent 方法调用后不会返回 Promise,也不涉及任何异步操作 ,所以通常不需要使用 await 关键字。

userEvent:旨在更贴近用户的实际操作,因此它经常涉及到一系列复杂的、可能是异步的事件。例如,当用户在输入框中输入文字时,这不仅仅是一个简单的同步操作。它包含了一系列的键盘和输入事件,这些事件可能会触发各种事件处理器,这些处理器本身可能是异步的。

1、fireEvent来自'@testing-library/react'userEvent来自@testing-library/user-event

2、fireEvent的清空 Input 输入框操作为fireEvent.change(input, {target: {value: ''}})userEvent的清空 Input 输入框操作为userEvent.type(input, '{backspace}')

3、fireEvent前不需要添加awaituserEvent需要。

总结:如果需要模拟简单的事件并需要完全控制这些事件的属性,fireEvent 是个好选择。而如果需要模拟更复杂或更接近真实用户行为的交互,userEvent 则更合适。

typescript 复制代码
describe('测试输入框的校验规则', () => {
	it('仅支持汉字、字母、数字和-_%.', async () => {
		const Com = <Index />
		const container = render(Com)
		const input = await screen.findByRole('textbox')
		fireEvent.change(input, {target: {value: '@'}})
	})
})
describe('测试输入框的校验规则', () => {
	it('仅支持汉字、字母、数字和-_%.', async () => {
		const Com = <Index />
		const container = render(Com)
		const input = await screen.findByRole('textbox')
		await userEvent.type(input, '@')
	})
})

Jest 测试案例

测试 Input 输入框的校验规则

当前 Input 输入框的校验规则:

(1)必填

(2)限长100

(3)仅支持汉字、字母、数字和-_%.

(4)必须以数字、字母或汉字开头

typescript 复制代码
const nameRules = ({
    label,
    max = 10,
    required = true,
}: {
    label: string
    max?: number
    required?: boolean
}): Rule[] => [
        { required, message: `${label}必填` },
        { type: 'string', max, message: `${label}限长${max}` },
        {
            pattern: /^([a-zA-Z0-9\u4E00-\u9FA5_.%-])*$/g,
            message: `${label}仅支持汉字、字母、数字和-_%.`,
        },
        {
            pattern: /^([0-9|a-zA-Z0-9|\u4E00-\u9FA5])/g,
            message: `${label}以数字、字母或汉字开头`,
        },
    ]

因被测试组件的复杂程度不同,测试同一个功能所用的 API 也不同

(1)被测试功能组件的简单版:

该组件只有基本的页面布局和nameRules校验规则

typescript 复制代码
/* eslint-disable react-hooks/rules-of-hooks */
import { nameRules } from '@/utils/constants'
import { Form, Input } from 'antd'

const myInput = () => {
    return (
        <Form>
            <Form.Item
                label="Username"
                name="username"
                // 校验规则
                rules={nameRules({
                    label: '名称',
                    required: true,
                })}
            >
                <Input />
            </Form.Item>
        </Form>
    )
}
export default myInput

在测试较简单的组件时,模拟用户操作可以使用fireEvent.change(),断言也无需包裹在waitFor中便可同步执行。

typescript 复制代码
/* eslint-disable no-undef */
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import Index from './index'
import '@testing-library/jest-dom'

describe('测试输入框的校验规则', () => {
    it('必填', async () => {
        // 渲染组件
        const Com = <Index />
        const container = render(Com)
        // findByRole不管前缀是screen还是container都可以成功
        const input = await screen.findByRole('textbox')
        // 在 input 输入框中输入1
        fireEvent.change(input, { target: { value: '1' } })
        // 清空 input
        fireEvent.change(input, { target: { value: '' } })
        // findByText参数必须是完整的文本,如果是子字符串,需要加上{exact: false}
        expect(await container.findByText('必填', { exact: false })).toBeInTheDocument()
    })
    it('仅支持汉字、字母、数字和-_%.', async () => {
        const Com = <Index />
        const container = render(Com)
        const input = await screen.findByRole('textbox')
        fireEvent.change(input, { target: { value: '@' } })
        expect(
            await container.findByText('仅支持汉字、字母、数字和-_%.', { exact: false })
        ).toBeInTheDocument()
    })
    it('以数字、字母或汉字开头', async () => {
        const Com = <Index />
        const container = render(Com)
        const input = await screen.findByRole('textbox')
        fireEvent.change(input, { target: { value: '-' } })
        expect(
            await container.findByText('以数字、字母或汉字开头', { exact: false })
        ).toBeInTheDocument()
    })
    it('限长', async () => {
        const Com = <Index />
        const container = render(Com)
        const input = await screen.findByRole('textbox')
        fireEvent.change(input, { target: { value: 'a'.repeat(101) } })
        expect(await container.findByText('限长', { exact: false })).toBeInTheDocument()
    })
    it('输入正常', async () => {
        const Com = <Index />
        const container = render(Com)
        const input = await screen.findByRole('textbox')
        fireEvent.change(input, { target: { value: '1' } })
        await waitFor(() => {
            expect(input.className).toMatch('ant-input-status-success')
        })
    })
})

(2)被测试功能组件的复杂版:

该组件是个集合组件,功能比较复杂,被测试的输入框只是其中一小部分内容。

因为组件存在 fetch 接口的请求,但是 jest 测试不会运行真实的 fetch 接口,所以需要 mock 数据,在本组件中通过在catch中给定初始数据。

在复杂环境下render组件时,需要 mock 渲染组件所需的各项参数,在本组件中id值是直接给定一个存在的 id ,onCancel方法 mock 一个空函数,Dn初始化数据。

此时模拟用户操作须使用await userEvent.type(),断言外须包裹await waitFor(() => {})

typescript 复制代码
/* eslint-disable no-undef */
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import ComplexIndex from './ComplexIndex'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'

describe('测试输入框的校验规则', () => {
    const onCancelMock = jest.fn()

    it('必填', async () => {
        // 渲染组件
        render(
            <ComplexIndex
                id="93e"
                onCancel={onCancelMock}
                Dn={{
                    dn1: 1,
                    dn2: '',
                }}
            />
        )
        const input = await screen.findByRole('textbox')
        // 在 input 输入框中输入"正常输入"
        await userEvent.type(input, '1')
        // 清空 input
        await userEvent.type(input, '{backspace}')
        // 异步等待断言执行
        await waitFor(() => {
            expect(screen.getByText('必填', { exact: false })).toBeInTheDocument()
        })
    })
    it('正常输入', async () => {
        render(
            <ComplexIndex
                id="93e"
                onCancel={onCancelMock}
                Dn={{
                    dn1: 1,
                    dn2: '',
                }}
            />
        )
        const input = await screen.findByRole('textbox')
        await userEvent.type(input, '正常输入')
        await waitFor(() => {
            expect(input.className).toMatch('ant-input-status-success')
        })
    })
    it('限长', async () => {
        render(
            <ComplexIndex
                id="93e"
                onCancel={onCancelMock}
                Dn={{
                    dn1: 1,
                    dn2: '',
                }}
            />
        )
        const input = await screen.findByRole('textbox')
        await userEvent.type(input, 'a'.repeat(101))
        await waitFor(() => {
            expect(screen.getByText('限长', { exact: false })).toBeInTheDocument()
        })
    })
    it('仅支持汉字、字母、数字和-_%.', async () => {
        render(
            <ComplexIndex
                id="93e"
                onCancel={onCancelMock}
                Dn={{
                    dn1: 1,
                    dn2: '',
                }}
            />
        )
        const input = await screen.findByRole('textbox')
        await userEvent.type(input, '@')
        await waitFor(() => {
            expect(
                screen.getByText('仅支持汉字、字母、数字和-_%.', { exact: false })
            ).toBeInTheDocument()
        })
    })
    it('以数字、字母或汉字开头', async () => {
        render(
            <ComplexIndex
                id="93e"
                onCancel={onCancelMock}
                Dn={{
                    dn1: 1,
                    dn2: '',
                }}
            />
        )
        const input = await screen.findByRole('textbox')
        await userEvent.type(input, '-')
        await waitFor(() => {
            expect(screen.getByText('以数字、字母或汉字开头', { exact: false })).toBeInTheDocument()
        })
    })
})

(3)获取原始 DOM 内容进行测试

Input 标签有aria-describedby属性,该属性的属性值是某个divid,该div下的div包含所有类型的报错字样。

typescript 复制代码
/* eslint-disable no-undef */
import { fireEvent, render, screen } from '@testing-library/react'
import Index from './index'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'

describe('测试输入框的校验规则', () => {
    it('仅支持汉字、字母、数字和-_%.', async () => {
        // 渲染被测组件
        const Com = <Index />
        const container = render(Com)
        // 获取input元素
        const input = await screen.findByRole('textbox')
        // 在input输入框中输入@
        await userEvent.type(input, '@')
        // 获取input元素
        const inputEl = document.querySelector("input[type='text']")
        // 获取input元素的所有属性
        const attributes = inputEl!.attributes
        let ariaDescribedby = ''
        for (let i = 0; i < attributes?.length; i++) {
            console.log(attributes[i].name, attributes[i].value)
            // 找到aria-describedby属性
            if (attributes[i].name === 'aria-describedby') {
                // 获取 aria-describedby 属性的值
                ariaDescribedby = attributes[i].value
            }
        }
        // div 的 id 值为 aria-describedby 属性的值
        const borderDiv = document.getElementById(ariaDescribedby)
        const childrenDiv = borderDiv?.querySelectorAll('div')
        childrenDiv?.forEach(div => {
            // 报错文本
            console.log(div.textContent)
        })
    })
})

Jest VSCode 插件

1、Jest

内置测试运行器,可以直接运行和调试 jest 测试,可以测试某个 describe 或者某个 describe 中的单个 it

相关推荐
天蓝色的鱼鱼16 分钟前
都2026年了还不会Vite插件开发?手写一个版本管理插件,5分钟包会!
前端·vite
苏武难飞28 分钟前
分享一个33号远征队的效果!
前端
鹏程十八少1 小时前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
亿元程序员1 小时前
这款值68亿的游戏,你不实战一下吗?安排!
前端
摸鱼的春哥2 小时前
Agent教程15:认识LangChain(中),状态机思维
前端·javascript·后端
明月_清风2 小时前
告别遮挡:用 scroll-padding 实现优雅的锚点跳转
前端·javascript
明月_清风2 小时前
原生 JS 侧边栏缩放:从 DOM 监听到底层优化
前端·javascript
万少11 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站13 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名15 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员