如何在 TypeScript 中测试 React 组件
介绍
编写应用程序时,测试对于确保代码按预期运行至关重要。在本指南中,您将学习如何使用 TypeScript、React 和 Jest 以惯用的方式快速编写测试。
测试时利用 TypeScript 有几个好处:
- 实现更好的测试重构,改善长期维护
- 确保组件使用和道具的一致性
- 减少测试中出现错误的可能性
编写单元测试和使用 TypeScript 并不互相排斥,一起使用时它们可以帮助您构建更易于维护的代码库。
我们将使用Jest,一个包括 React 在内的 JavaScript 项目的流行测试框架。
使用 React 测试库
Jest 将负责运行测试和处理断言,但由于我们正在测试 React,因此我们需要一些测试实用程序。
有两个流行的 React 测试库:Enzyme和React Testing Library。
在本指南中,我们将使用 React 测试库测试 React 组件,因为它提供了一种简单直接的方法来测试组件,从而促进良好的测试实践。
React 测试库提倡以下几种实践:
- 避免测试内部组件状态
- 测试组件如何呈现
这两种做法有助于将测试重点放在行为和用户交互上,将组件的内部视为不应暴露的“黑匣子”。
“你的测试越接近软件的使用方式,你就越有信心。”—— RTL 的创建者Kent C. Dodds。
配置 Jest 和 React 测试库
从现有的 React 和 TypeScript 项目开始,我们可以添加Jest和React Testing Library的依赖项:
npm install @types/jest @testing-library/react @testing-library/jest-dom jest ts-jest
这将安装支持 TypeScript 的 Jest 和 React Testing Library。
在项目根目录添加一个新的jest.config.js文件:
module.exports = {
// The root of your source code, typically /src
// `<rootDir>` is a token Jest substitutes
roots: ["<rootDir>/src"],
// Jest transformations -- this adds support for TypeScript
// using ts-jest
transform: {
"^.+\\.tsx?$": "ts-jest"
},
// Runs special logic, such as cleaning up components
// when using React Testing Library and adds special
// extended assertions to Jest
setupFilesAfterEnv: [
"@testing-library/react/cleanup-after-each",
"@testing-library/jest-dom/extend-expect"
],
// Test spec file resolution pattern
// Matches parent folder `__tests__` and filename
// should contain `test` or `spec`.
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
// Module file extensions for importing
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"]
};
这是一个典型的 Jest 配置,但有一些额外的修改:
- 通过ts-jest包添加TypeScript 支持
- 使用 React Testing Library 时的DOM 清理
- 使用 React Testing Library 时的扩展断言
向package.json添加新的npm脚本:
{
...
"scripts": {
...
"test": "jest",
"test:watch": "jest --watch"
}
}
Jest 支持强大的“监视”模式,可以在您开发时以快速的方式重新运行更改的测试。
根据此配置,测试应该组织到顶级src目录下的__tests__文件夹中。
确保您的tsconfig.json已启用esModuleInterop标志,以兼容 Jest(和 Babel):
{
"compilerOptions": {
"esModuleInterop": true
}
}
为了确保所有测试文件都可以使用附加的 Jest 匹配器,请创建src/globals.d.ts并导入匹配器:
import "@testing-library/jest-dom/extend-expect";
测试基本组件
对于本指南,我们将测试一个具有内部状态并使用 React hooks 的基本组件来展示如何编写一组测试。
创建包含以下内容的src/LoginForm.tsx :
import React from "react";
export interface Props {
shouldRemember: boolean;
onUsernameChange: (username: string) => void;
onPasswordChange: (password: string) => void;
onRememberChange: (remember: boolean) => void;
onSubmit: (username: string, password: string) => void;
}
function LoginForm(props: Props) {
const [username, setUsername] = React.useState("");
const [password, setPassword] = React.useState("");
const [remember, setRemember] = React.useState(props.shouldRemember);
const handleUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setUsername(value);
props.onUsernameChange(value);
};
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
setPassword(value);
props.onPasswordChange(value);
};
const handleRememberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { checked } = e.target;
setRemember(checked);
props.onRememberChange(checked);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
props.onSubmit(username, password);
};
return (
<form data-testid="login-form" onSubmit={handleSubmit}>
<label htmlFor="username">Username:</label>
<input
data-testid="username"
type="text"
name="username"
value={username}
onChange={handleUsernameChange}
/>
<label htmlFor="password">Password:</label>
<input
data-testid="password"
type="password"
name="password"
value={password}
onChange={handlePasswordChange}
/>
<label>
<input
data-testid="remember"
name="remember"
type="checkbox"
checked={remember}
onChange={handleRememberChange}
/>
Remember me?
</label>
<button type="submit" data-testid="submit">
Sign in
</button>
</form>
);
}
export default LoginForm;
这是一个简单的登录表单,包含用户名、密码和复选框。它使用useState钩子来维护内部状态,并使用shouldRemember属性来设置复选框的默认状态。
创建一个新的测试文件src/__tests__/LoginForm.test.tsx:
import React from "react";
import { render, fireEvent, waitForElement } from "@testing-library/react";
import LoginForm, { Props } from "../LoginForm";
describe("<LoginForm />", () => {
test("should display a blank login form, with remember me checked by default", async () => {
// ???
});
});
这是我们测试套件的骨架。我们首先从@testing-library/react导入所需的实用程序:
- render帮助渲染组件并返回找到的辅助方法。
- fireEvent用于模拟 DOM 元素上的事件。
- waitForElement在等待 UI 改变发生时很有用。
我们已经定义了一个测试,但它没有实现。要运行测试套件,请启动测试运行器:
npm run test:watch
这将在我们实施测试时监视变化。由于没有断言,因此第一个测试通过:
请记住,我们要测试重要的行为,因此第一个测试将确保我们为空白表单呈现用户名、密码和“记住我”复选框。
一个有用的技巧是使用渲染辅助函数封装您正在测试的组件的渲染,这样您就可以处理 props 覆盖并使您的测试更易于维护:
function renderLoginForm(props: Partial<Props> = {}) {
const defaultProps: Props = {
onPasswordChange() {
return;
},
onRememberChange() {
return;
},
onUsernameChange() {
return;
},
onSubmit() {
return;
},
shouldRemember: true
};
return render(<LoginForm {...defaultProps} {...props} />);
}
在这里,我们利用 TypeScript 来确保我们的 props 一致地应用于LoginForm组件。我们首先定义一些“默认”props,然后将传递到函数中的其他 props 作为覆盖。覆盖 props 的类型为Partial<Props>,因为它们是可选的。
如果Props接口发生变化,TypeScript 将抛出编译器错误,并且测试助手将需要更新,以确保我们的测试保持更新。
React Testing Library 提供了一种使用助手快速查找元素的方法。
我们将使用findByTestId通过元素的
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~