赞
踩
具体框架下(cra,umi,antd-pro等)环境的配置略有不同,后续单元测试的写法都是雷同的;环境的配置在面试时不是重点,如何写单测本身才是!
本例以Vite+React+TS
为例进行说明。
首先,你需要安装Jest本身以及一些与React和TypeScript相关的依赖。打开终端,切换到你的项目目录,然后运行以下命令:
npm install --save-dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom
这将安装Jest、Jest的TypeScript支持、React的测试库以及必要的类型定义。
接下来,你需要创建一个Jest配置文件。你可以在项目根目录下创建一个jest.config.js
文件,并添加以下配置:
- module.exports = {
- // 使用ts-jest预设,这个预设包含了处理TypeScript文件所需的所有配置
- preset: 'ts-jest',
-
- // 设置测试环境为jsdom,jsdom模拟了一个浏览器环境,允许在Node环境下运行浏览器特定的API
- testEnvironment: 'jsdom',
-
- // 在每个测试文件运行之后,立即执行指定的脚本文件,这里是项目根目录下的src/setupTests.ts
- // 通常用于全局的测试设置,比如配置enzyme或jest-dom等
- setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
-
- // 配置模块名称映射,用于将导入语句中的别名映射到实际的文件路径
- moduleNameMapper: {
- // 将@components别名映射到src/components目录
- // 这样在测试中可以使用@components/xxx来引入组件
- // 在tsconfig.json中也需要进行对称的配置
- '^@components/(.*)$': '<rootDir>/src/components/$1',
- },
-
- // 配置文件转换规则,告诉Jest如何处理项目中的不同类型的文件
- transform: {
- // 使用ts-jest处理.ts和.tsx文件
- // 这允许Jest理解TypeScript语法并将其转换为JavaScript
- '^.+\\.(ts|tsx)$': 'ts-jest',
-
- // 使用babel-jest处理.js和.jsx文件
- // 这允许Jest通过Babel转换这些文件,支持ES6语法和React JSX
- '^.+\\.(js|jsx)$': 'babel-jest',
- },
-
- // 指定Jest在转换过程中应该忽略的文件模式
- // 这里配置为忽略node_modules目录下的所有文件,这些文件通常不需要转换
- transformIgnorePatterns: ['<rootDir>/node_modules/'],
- };
这个配置做了几件事情:
使用ts-jest预设来处理TypeScript文件。
设置测试环境为jsdom,这对于模拟浏览器环境的React测试很有用。
指定了一个setupFilesAfterEnv配置,这允许你在每次测试之前自动加载一些配置或全局mocks。
moduleNameMapper用于解析模块别名,这在你的项目中使用了如Webpack别名时非常有用。
transform配置告诉Jest如何处理.ts、.tsx、.js和.jsx文件。
在src目录下创建一个setupTests.ts文件,用于配置或添加一些在测试之前需要运行的代码。例如,你可以在这里导入@testing-library/jest-dom以扩展Jest的断言库:
import '@testing-library/jest-dom';
现在可以开始编写测试了。
此处我们写一个缓存执行结果的闭包
src/utils/functionalUtil.ts
- export function cacheIt(fn: Function) {
- const cache = new Map<string, any>();
-
- return function (...args: any[]) {
- const key = JSON.stringify(args);
- if (!cache.has(key)) {
- const result = fn.apply(null, args);
- cache.set(key, result);
- return result;
- }
- return cache.get(key);
- }
- }
使用jest断言语法编写测试脚本
tests/utils/functionalUtil.test.ts
- // 从项目的utils/functionalUtil模块中导入cacheIt函数
- import { cacheIt } from '@/utils/functionalUtil';
-
- // 使用describe定义一组测试用例,这组测试用例的目的是测试cacheIt函数
- describe('cacheIt', () => {
-
- // 定义一个测试用例,测试cacheIt是否能正确缓存函数结果
- it('should cache and return the result for the same arguments', () => {
- // 使用jest.fn创建一个模拟函数add,模拟一个加法操作
- const add = jest.fn((a: number, b: number) => a + b);
-
- // 使用cacheIt函数对add函数进行缓存处理
- const cachedAdd = cacheIt(add);
-
- // 调用cachedAdd函数两次,传入相同的参数(2, 3),并期望返回值为5
- expect(cachedAdd(2, 3)).toBe(5);
- expect(cachedAdd(2, 3)).toBe(5);
-
- // 验证add函数只被实际调用了一次,因为第二次调用时结果应该是从缓存中获取的
- expect(add).toHaveBeenCalledTimes(1);
-
- // 再次调用cachedAdd函数,但这次传入不同的参数(3, 4),并期望返回值为7
- expect(cachedAdd(3, 4)).toBe(7);
-
- // 验证add函数此时被调用了两次,因为传入了新的参数组合
- expect(add).toHaveBeenCalledTimes(2);
- });
-
- // 定义另一个测试用例,测试对于相同的参数是否总是返回缓存的结果
- it('should return cached result for the same arguments called multiple times', () => {
- // 使用jest.fn创建一个模拟函数multiply,模拟一个乘法操作
- const multiply = jest.fn((a: number, b: number) => a * b);
-
- // 使用cacheIt函数对multiply函数进行缓存处理
- const cachedMultiply = cacheIt(multiply);
-
- // 分别两次调用cachedMultiply函数,传入相同的参数(4, 5)
- const firstCallResult = cachedMultiply(4, 5);
- const secondCallResult = cachedMultiply(4, 5);
-
- // 验证两次调用的返回值都为20
- expect(firstCallResult).toBe(20);
- expect(secondCallResult).toBe(20);
-
- // 验证multiply函数只被实际调用了一次,因为第二次调用时结果应该是从缓存中获取的
- expect(multiply).toHaveBeenCalledTimes(1);
- });
- });
这组测试用例通过模拟函数和cacheIt函数的组合使用,验证了cacheIt能够正确地缓存函数调用结果,并在相同参数的后续调用中返回缓存的结果,从而减少实际函数调用的次数。
最后,你需要在package.json
中添加一个脚本来运行测试:
- {
- "scripts": {
- "test": "jest"
- }
- }
现在,你可以通过运行以下命令来执行你的测试:
npm test
这些步骤应该帮助你在使用Vite、React和TypeScript的项目中集成Jest进行单元测试。记得根据你的项目需求调整配置和测试。
编写一个模拟异步返回数据的API函数
src/apis/modelOne.ts
- export async function fetchData(url: string): Promise<any> {
- try {
- const response = await fetch(url);
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- const data = await response.json();
- return data;
- } catch (error) {
- throw new Error(`Fetching data failed: ${error}`);
- }
- }
编写测试脚本
tests/apis/modelOne.test.ts
- import { fetchData } from '@/apis/modelOne'; // 从modelOne模块导入fetchData函数
-
- // 使用jest来模拟全局的fetch
- global.fetch = jest.fn(() =>
- Promise.resolve({
- ok: true, // 模拟fetch请求成功返回
- json: () => Promise.resolve({ message: 'Success' }), // 模拟返回的JSON数据
- })
- ) as jest.Mock; // 将模拟的fetch函数强制类型转换为jest.Mock类型
-
- beforeEach(() => {
- // 在每个测试用例运行之前清除模拟的调用和实例
- (fetch as jest.Mock).mockClear(); // 清除fetch模拟函数的调用记录
- });
-
- test('fetchData returns data on successful fetch', async () => {
- const data = await fetchData('https://example.com/data'); // 调用fetchData函数
-
- expect(fetch).toHaveBeenCalledTimes(1); // 验证fetch是否被调用了一次
- expect(fetch).toHaveBeenCalledWith('https://example.com/data'); // 验证fetch是否用正确的URL被调用
- expect(data).toEqual({ message: 'Success' }); // 验证fetchData函数的返回值是否符合预期
- });
-
- test('fetchData throws an error when fetch fails', async () => {
- (fetch as jest.Mock).mockRejectedValue(new Error('Failed to fetch')); // 模拟fetch请求失败
-
- await expect(fetchData('https://example.com/data')).rejects.toThrow('Fetching data failed: Error: Failed to fetch'); // 验证当fetch失败时,fetchData函数是否按预期抛出错误
- });
这个测试文件主要做了两件事:1. 使用jest.fn()模拟全局的fetch函数,以便在不发出真实网络请求的情况下测试fetchData函数的行为。模拟的fetch函数可以根据需要返回成功或失败的响应。2. 定义了两个测试用例:
第一个测试用例验证当fetch成功时,fetchData函数是否正确返回数据。
第二个测试用例验证当fetch失败时,fetchData函数是否抛出了预期的错误。
src/components/Hello.tsx
- export default function Hello({ name }: { name: string }) {
- return (
- <h1>Hello, {name}! </h1>
- )
- }
tests/components/Hello.test.tsx
- // 从@testing-library/react库中导入render和screen工具
- import { render, screen } from '@testing-library/react';
- // 从项目的components目录中导入Hello组件
- import Hello from '@components/Hello';
-
- // 定义一个测试用例,测试名称为'renders hello message'
- test('renders hello message', () => {
-
- // 使用render函数渲染Hello组件,并传入props,这里传入的name为"world"
- render(<Hello name="world" />);
-
- // 使用screen.getByText查询函数来查找页面上的文本内容
- // 这里使用正则表达式/i来忽略大小写,匹配文本"hello, world!"
- const helloElement = screen.getByText(/hello, world!/i);
-
- // 使用expect函数和toBeInTheDocument匹配器来断言
- // 检查helloElement是否成功渲染在了文档中
- expect(helloElement).toBeInTheDocument();
- });
这个测试用例的目的是验证Hello组件是否能够根据传入的name prop正确渲染出"hello, world!"这个消息。通过@testing-library/react提供的render函数来渲染组件,并使用screen.getByText来查询渲染结果中是否包含了期望的文本内容。最后,使用expect和toBeInTheDocument来断言查询到的元素确实存在于文档中,从而验证组件的渲染行为。
src/components/MessageFetcher.tsx
- import /* React, */ { useState } from 'react';
-
- export const MessageFetcher = () => {
- const [message, setMessage] = useState('');
-
- const fetchMessage = async () => {
- try {
- const response = await fetch('https://api.example.com/message');
- const data = await response.json();
- setMessage(data.message);
- } catch (error) {
- console.error('Fetching message failed:', error);
- setMessage('Error fetching message');
- }
- };
-
- return (
- <div>
- <button onClick={fetchMessage}>Fetch Message</button>
- {message && <p>{message}</p>}
- </div>
- );
- };
tests/components/MessageFetcher.test.tsx
- // 从@testing-library/react库中导入render, screen, fireEvent, 和 waitFor工具
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-
- // 导入@testing-library/jest-dom以获得额外的jest断言方法
- import '@testing-library/jest-dom';
-
- // 从项目的components目录中导入MessageFetcher组件
- import { MessageFetcher } from '@components/MessageFetcher';
-
- // 使用jest.fn()模拟全局的fetch函数
- global.fetch = jest.fn(() =>
- Promise.resolve({
- json: () => Promise.resolve({ message: 'Hello from the API' }), // 模拟fetch请求成功,并返回一个对象,该对象包含一个json方法,json方法返回一个解析为包含特定消息的对象的Promise
- })
- ) as jest.Mock; // 将模拟的fetch函数强制类型转换为jest.Mock类型
-
- // 使用describe函数定义一组相关的测试
- describe('MessageFetcher', () => {
- beforeEach(() => {
- // 在每个测试用例运行之前,使用mockClear方法清除fetch模拟函数的调用记录和实例
- (fetch as jest.Mock).mockClear();
- });
-
- it('fetches and displays the message', async () => {
- // 使用render函数渲染MessageFetcher组件
- render(<MessageFetcher />);
-
- // 使用fireEvent.click模拟用户点击操作,触发获取消息的按钮
- fireEvent.click(screen.getByText('Fetch Message'));
-
- // 使用waitFor异步等待,直到期望的断言通过
- await waitFor(
- // 使用expect函数和toBeInTheDocument断言方法来检查页面上是否成功显示了API返回的消息
- () => expect(screen.getByText('Hello from the API')).toBeInTheDocument()
- );
-
- // 检查fetch是否被准确地调用了一次
- expect(fetch).toHaveBeenCalledTimes(1);
- });
- });
这个测试文件主要做了以下几件事:
行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群: 759968159,里面有各种测试开发资料和技术可以一起交流哦。
最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】
软件测试面试文档
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。