上一篇文章用vitest和React测试库探索了how to test React Hooks as a standalone unit。在这篇文章中,我们将继续学习如何以可维护且可扩展的方式利用单位测试反应组件。
目录
- Table of Contents
- Prerequisites
- Extending Vitest's expect method
- Testing the Movies component with mocks
- Testing the search input's functionality
- Cleaning up the mocks after each test
- Cleaning up the DOM after each test
- Summary
先决条件
您应该设置并运行一个React项目。推荐的方法是使用命令npm create vite@latest
以Vite作为捆绑管理工具来初始化项目。
进行测试,我们需要安装以下依赖项:
- Vitest作为单元测试框架
- JSDOM作为运行我们的测试的DOM环境
- React Testing Library作为React测试实用程序
为此,我们运行以下命令:
npm install -D vitest jsdom @testing-library/react
#OR
yarn add -D vitest jsdom @testing-library/react
在vitest.config.js
中(或用于Vite Projects的vite.config.js
),我们添加以下test
对象:
//...
export default defineConfig({
test: {
global: true,
environment: 'jsdom',
},
})
我们还将新的test:unit
命令添加到package.json
文件中以进行单元测试如下:
"scripts": {
"test:unit": "vitest --root src/",
}
接下来,我们将进行额外的设置,以使vitest声称dom元素。
扩展Vitest的期望方法
vitest提供了与expect
一起使用的基本断言方法,用于主张值。但是,它没有诸如toBeInTheDocument()
或toHaveTextContent()
之类的DOM元素的断言方法。对于这种方法,我们可以安装@testing-library/jest-dom
软件包,并将expect
方法从vitest扩展到该软件包中的matchers
中的断言方法。
为此,我们将在项目的根目录中创建一个setupTest.js
文件,并添加以下代码:
/**setupTest.js */
import { expect } from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
expect.extend(matchers);
在vitest.config.js
中,我们可以将setupTest.js
文件添加到test.setupFiles
字段:
//vitest.config.js
/**... */
test: {
/**... */
setupFiles: './setupTest.js',
},
/**... */
,使用此设置,expect()
现在将具有测试React组件所需的所有DOM断言方法。
让我们看一下如何与vitest和React测试库测试反应组件。
用模拟测试电影组件
对于本节,我们将查看一个简单的组件-Movies
-显示具有以下功能的电影列表:
- 该组件从外部来源获取电影列表。
- 用户可以通过标题搜索电影。
以下屏幕截图显示了组件在UI上的外观:
Movies
组件的实现示例如下:
export const Movies = () => {
const { movies } = useMovies();
const {searchTerm, setSearchTerm, filteredItems: filteredMovies} = useSearch(movies);
return (
<section>
<div>
<label htmlFor="search">Search</label>
<input
type="search" id="search" value={searchTerm}
data-testid="search-input-field"
onChange={event => setSearchTerm(event.target.value)}
/>
</div>
<ul data-testid="movies-list">
{filteredMovies.map((movie, index) => (
<li key={index}>
<article>
<h2>{movie.title}</h2>
<p>Release on: {movie.release_date}</p>
<p>Directed by: {movie.director}</p>
<p>{movie.opening_crawl}</p>
</article>
</li>
))}
</ul>
</section>
)
};
我们将从vitest(以vi
为vitest实例)中的vi.spyOn()
方法进行间谍和嘲笑useMovies
和useSearch
钩子,如以下代码:
import * as useMoviesHooks from '../hooks/useMovies';
import * as useSearchHooks from '../hooks/useSearch';
describe('Movies', () => {
const useMoviesSpy = vi.spyOn(useMoviesHooks, 'useMovies');
const useSearchSpy = vi.spyOn(useSearchHooks, 'useSearch');
});
我们将使用mockReturnValue
方法模拟其返回值,如下:
describe('Movies', () => {
/**... */
it('should render the app', () => {
const items = [{
title: 'Star Wars',
release_date: '1977-05-25',
director: 'George Lucas',
opening_crawl: 'It is a period of civil war.'
}];
useMoviesSpy.mockReturnValue({
movies: items,
});
useSearchSpy.mockReturnValue({
searchTerm: '',
setSearchTerm: vi.fn(),
filteredItems: items
});
/**... */
});
})
然后,我们将使用@testing-library/react
的render
方法渲染Movies
组件,并断言该组件如预期的那样呈现电影列表,如下所示:
import { describe, it, expect, vi } from 'vitest';
import { Movies } from './Movies';
import { render } from '@testing-library/react';
describe('Movies', () => {
/**... */
it('should render the the list of movies', () => {
/**... */
const { getByTestId } = render(<Movies />);
expect(
getByTestId('movies-list').children.length
).toBe(items.length);
});
})
getByTestId
方法将检索其data-testid
属性值等于movies-list
的元素,然后我们可以主张其子女以等于我们模拟的items
阵列的长度。
使用data-testid
属性值是识别用于测试目的的DOM元素并避免影响组件在生产和测试中的实现的好习惯。
接下来,我们将测试Movies
中搜索钩的集成。
测试搜索输入的功能
我们首先仅嘲笑useMovies
钩以返回一组电影,如下所示:
it('should change the filtered items when the search term changes', () => {
const items = [
{ title: 'Star Wars' },
{ title: 'Star Trek' },
{ title: 'Starship Troopers' }
];
useMoviesSpy.mockReturnValue({
movies: items,
isLoading: false,
error: null
});
});
我们渲染Movies
组件,并使用getByTestId
方法使用data-testid
检索搜索输入字段:
it('should change the filtered items when the search term changes', () => {
/**... */
const { getByTestId } = render(<Movies />);
const searchInput = getByTestId('search-input-field');
});
要测试Movies
UI中的搜索功能,我们将使用@testing-library/react
的以下两个方法:
-
fireEvent.change()
-要在搜索输入字段上模拟用户事件change
。 -
act()
-要围绕执行用户事件模拟的执行,并确保所有更新适用于DOM,然后再对UI上显示的项目数量进行断言。
import { fireEvent, render, act } from '@testing-library/react';
it('should change the filtered items when the search term changes', () => {
/**... */
act(() => {
fireEvent.change(searchInput, { target: { value: 'Wars' } });
})
expect(
getByTestId('movies-list').children.length
).toBe(1);
});
这样,我们已经测试了用户与搜索输入字段的交互以及组件对用户输入的响应。
但是,如果您在上一个测试后运行测试,则该测试将失败,因为useSearch
的最后一个模拟值仍有生效。我们必须在每次测试后清洁并恢复原始实现,以确保每个测试用例隔离模拟值。我们将在下一部分中这样做。
每次测试后清理模拟
要清理我们间谍的每个钩子的任何模拟值,我们将触发mockClear()
如下:
afterEach(() => {
useMoviesSpy.mockClear();
useSearchSpy.mockClear();
});
使用此代码,每次测试运行后,Vitest将清除任何现有的模拟价值或间谍钩的实现,为下一次测试准备。另外,我们可以使用mockRestore()
来恢复非嵌入式实现。
接下来,我们将采用类似的方法,为每个测试套件清洁DOM,但对于所有测试套件。
每次测试后清理DOM
在setupTest.js
中,我们可以使用@testing-library/react
运行cleanup
方法在每个测试后清理DOM,使用Vitest的afterEach
方法,如下所示:
/**setupTest.js */
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
/**... */
afterEach(() => {
cleanup();
});
这样做,我们可以在每次测试之前确保DOM清洁,并将其应用于所有测试套件。
概括
本文向我们展示了如何使用React测试库和Vitest软件包测试反应组件,并使用适当的模拟方法和适当的测试方法进行测试。
我们可以将测试从示例测试扩展到涵盖更多方案,例如在加载电影时测试加载状态或错误状态,或为搜索输入添加更多过滤器选项。使用正确的组件和钩子结构,我们可以以有组织的可扩展方式创建我们的测试系统。
ð如果您想赶上我有时候,请在Twitter上关注我| Facebook。 | Buy me a coffee
ð通过我的新书Learning Vue了解Vue。早期版本现在可以使用!