如何在 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通过元素的
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
 
 
                                 
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                     
                                 
                             
                                     
                                     
                                     
                                     
     
    
 
             
   
        
请先 登录后发表评论 ~