基于vite创建的react18项目的单元测试

题外话

最近一个小伙伴进了字节外包,第一个活就是让他写一个单元测试。

嗯,说实话,在今天之前我只知道一些理论,但是并没有实操过,于是我就试验了一下。

通过查询资料,大拿们基本都说基于vite的项目,用vitest进行测试比较方便一写。

闲话不多说,步入正题。

1、下载依赖

在vscode终端输入以下命令:

npm install --save-dev vitest @testing-library/react @testing-library/jest-dom
  1. --save-dev: 这个标志表示将这些包添加为开发依赖(devDependencies)。这些依赖只在开发环境中使用,而不会被包含在生产环境中。例如,测试框架和工具通常只在开发时需要,而不需要在生产环境中。

  2. vitest: 这是一个快速的单元测试框架,类似于 Jest,但专为 Vite 生态系统设计。它支持现代 JavaScript 特性,并且与 Vite 无缝集成,非常适合用于测试 Vite 创建的项目。

  3. @testing-library/react: 这是一个用于测试 React 组件的库,提供了一组 API,使得编写测试变得简单而直观。它鼓励以用户的方式来测试组件,而不是实现细节,从而提高测试的可靠性和可维护性。

  4. @testing-library/jest-dom: 这是一个为 Jest 提供的自定义匹配器库,增强了 Jest 的断言功能,使得你可以使用更自然的语法来进行 DOM 相关的断言。例如,你可以使用 toBeInTheDocument() 来检查某个元素是否在文档中,而不需要写复杂的查询逻辑。

2、创建testSetup.js文件

文件里只有一行代码:

import '@testing-library/jest-dom';

3、配置vite.config.js文件

代码如下:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './testSetup.js',
  },
})
  • test: 这是 Vitest 的配置部分。
  • globals: true: 这个选项表示在测试文件中可以使用全局的测试函数,比如 describetestexpect 等,而不需要每次都导入它们。
  • environment: 'jsdom': 这个选项指定测试运行在 jsdom 环境中。jsdom 是一个 JavaScript 实现的 DOM,用于模拟浏览器环境,这样你可以在 Node.js 中运行测试并且测试涉及 DOM 操作的代码。
  • setupFiles: './testSetup.js': 这个选项指定一个设置文件,在测试运行之前会执行。在第二步中我只是引入了一个包。

4、添加脚本

在项目的 package.json 中添加测试脚本:

"scripts": {
  "test": "vitest"
}

5、创建测试文件

在你的组件目录下,创建一个与组件同名的测试文件,通常以 .test.tsx 结尾。例如,如果你有一个 Wjllogin.jsx 组件,你可以在同一目录下创建 Wjllogin.test.jsx

我的demo中的Wjllogin.jsx中的代码如下:

import React, { useState } from "react"; // 导入 React 和 useState Hook
import "./wjs.scss"; // 导入样式文件
import "animate.css"; // 导入动画效果库
import { wjllogin } from "../axiosAPI/wjl"; // 导入用于登录的 API 函数
import { useNavigate } from "react-router-dom"; // 导入路由导航 Hook
import { message } from "antd"; // 导入 Ant Design 的消息提示组件

// 定义 Wjllogin 组件
export default function Wjllogin() {
	let navigate = useNavigate(); // 初始化路由导航
	let [name, setName] = useState(""); // 定义状态变量 name 和更新函数 setName
	let [card, setCard] = useState(""); // 定义状态变量 card 和更新函数 setCard
	let [tid, setTid] = useState(""); // 定义状态变量 tid 和更新函数 setTid

	const [messageApi, contextHolder] = message.useMessage(); // 使用 Ant Design 的消息提示 API

	// 定义登录函数
	let login = async () => {
		// 调用 wjllogin API 进行登录
		let {
			data: { code, sid, clazz, sname }, // 解构 API 返回的数据
		} = await wjllogin({ name, card, tid }); // 传递姓名、身份证号和学号到 API

		// 检查返回的状态码
		if (code === 200) {
			// 登录成功,保存用户信息到 sessionStorage
			sessionStorage.setItem("sid", sid); // 保存会话 ID
			sessionStorage.setItem("clazz", clazz); // 保存班级信息
			sessionStorage.setItem("token", "token"); // 保存 token(这里是示例,实际应从 API 获取)
			sessionStorage.setItem("sname", sname); // 保存姓名

			// 显示成功消息
			messageApi.open({
				type: "success",
				content: "登录成功, 即将跳转至主页",
			});

			// 设置延迟后跳转到主页
			setTimeout(() => {
				navigate("/wjlhome"); // 跳转到主页
			}, 2000);
		} else {
			// 登录失败,显示错误消息
			messageApi.open({
				type: "error",
				content: "登录失败,请检查姓名、身份证号或学号",
			});
		}
	};

	// 组件的 JSX 结构
	return (
		<div className="examlogin animate__animated animate__slideInLeft">
			{" "}
			{/* 主容器,包含动画效果 */}
			<div className="top">
				{" "}
				{/* 顶部区域 */}
				<img
					className="main"
					src="https://cdn7.axureshop.com/demo/2001850/images/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95/u2853.svg"
					alt=""
				/>{" "}
				{/* 主图标 */}
				<img
					className="x"
					src="https://cdn7.axureshop.com/demo/2001850/images/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95/u2854.svg"
					alt=""
				/>{" "}
				{/* 副图标 */}
			</div>
			<div className="title">
				{" "}
				{/* 标题区域 */}
				培训学院在线考试系统 {/* 系统名称 */}
				<span className="lessfont">考生版</span> {/* 子标题 */}
			</div>
			<div className="form">
				{" "}
				{/* 表单区域 */}
				<p>
					<label htmlFor="name">考生姓名:</label>
					<input
						id="name"
						value={name}
						onChange={(e) => setName(e.target.value)}
					/>
				</p>
				<p>
					<label htmlFor="card">身份证号:</label>
					<input
						id="card"
						type="text" // 输入框类型
						value={card} // 绑定到 card 状态
						onChange={(e) => {
							setCard(e.target.value); // 更新 card 状态
						}}
					/>
				</p>
				<p>
					<label htmlFor="tid">学号:</label> {/* 学号标签 */}
					<input
						id="tid"
						type="text" // 输入框类型
						value={tid} // 绑定到 tid 状态
						onChange={(e) => {
							setTid(e.target.value); // 更新 tid 状态
						}}
					/>
				</p>
				<p>
					<button
						className="btn" // 按钮样式
						onClick={() => {
							login(); // 点击按钮时调用 login 函数
						}}>
						登录 {/* 按钮文本 */}
					</button>
				</p>
				{contextHolder} {/* 显示消息提示的容器 */}
			</div>
		</div>
	);
}

测试代码Wjllogin.test.jsx中的代码如下:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; // 导入 MemoryRouter
import Wjllogin from './Wjllogin'; // 根据你的文件路径调整
import { wjllogin } from '../axiosAPI/wjl'; // 导入登录 API
import { describe, it, beforeEach, vi } from 'vitest'; // 导入 Vitest 的函数

// Mock the API call
vi.mock('../axiosAPI/wjl', () => ({
  wjllogin: vi.fn(),
}));

describe('Wjllogin Component', () => {
  beforeEach(() => {
    // 清除所有的 mocks
    vi.clearAllMocks();
  });

  it('renders Wjllogin component', () => {
    render(
      <MemoryRouter>
        <Wjllogin />
      </MemoryRouter>
    );
    
    // Check if elements are rendered
    expect(screen.getByText(/考生姓名:/)).toBeInTheDocument();
    expect(screen.getByText(/身份证号:/)).toBeInTheDocument();
    expect(screen.getByText(/学号:/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /登录/i })).toBeInTheDocument();
  });

  it('successful login', async () => {
    // Mock the API response for a successful login
    wjllogin.mockResolvedValueOnce({
      data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },
    });

    render(
      <MemoryRouter>
        <Wjllogin />
      </MemoryRouter>
    );

    // Fill in the input fields
    fireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });
    fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });
    fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });

    // Click the login button
    fireEvent.click(screen.getByRole('button', { name: /登录/i }));

    // Wait for the success message to appear
    await waitFor(() => {
      expect(screen.getByText(/登录成功, 即将跳转至主页/)).toBeInTheDocument();
    });

    // Check if sessionStorage is set (you may need to mock sessionStorage)
    expect(sessionStorage.getItem('sid')).toBe('123');
    expect(sessionStorage.getItem('clazz')).toBe('A1');
    expect(sessionStorage.getItem('sname')).toBe('John Doe');
  });

  it('failed login', async () => {
    // Mock the API response for a failed login
    wjllogin.mockResolvedValueOnce({
      data: { code: 400 },
    });

    render(
      <MemoryRouter>
        <Wjllogin />
      </MemoryRouter>
    );

    // Fill in the input fields
    fireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });
    fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });
    fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });

    // Click the login button
    fireEvent.click(screen.getByRole('button', { name: /登录/i }));

    // Wait for the error message to appear
    await waitFor(() => {
      expect(screen.getByText(/登录失败,请检查姓名、身份证号或学号/)).toBeInTheDocument();
    });
  });
});

解释代码

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; // 导入 MemoryRouter
import Wjllogin from './Wjllogin'; // 根据你的文件路径调整
import { wjllogin } from '../axiosAPI/wjl'; // 导入登录 API
import { describe, it, beforeEach, vi } from 'vitest'; // 导入 Vitest 的函数
  • @testing-library/react: 这个库提供了一些函数,用于渲染组件和进行交互测试。
  • MemoryRouter: 这是 React Router 提供的一个组件,用于在测试中模拟路由环境。
  • Wjllogin: 这是被测试的组件,假设它是一个登录表单。
  • wjllogin: 这是一个从 API 模块导入的函数,用于处理登录请求。
  • describeitbeforeEachvi: 这些是 Vitest 提供的函数,用于组织测试用例和创建 mock 函数。
vi.mock('../axiosAPI/wjl', () => ({
  wjllogin: vi.fn(),
}));
  • 这里使用 vi.mock 来模拟 wjllogin 函数,以便在测试中控制其返回值,而不实际调用 API。
describe('Wjllogin Component', () => {

}
  • describe 用于将相关的测试用例组织在一起,便于管理和阅读。
beforeEach(() => {
  vi.clearAllMocks();
});
  • beforeEach 在每个测试用例执行之前调用,确保每个测试用例都在干净的状态下运行,避免测试之间的相互影响。

测试用例

it('renders Wjllogin component', () => {
  render(
    <MemoryRouter>
      <Wjllogin />
    </MemoryRouter>
  );

  // Check if elements are rendered
  expect(screen.getByText(/考生姓名:/)).toBeInTheDocument();
  expect(screen.getByText(/身份证号:/)).toBeInTheDocument();
  expect(screen.getByText(/学号:/)).toBeInTheDocument();
  expect(screen.getByRole('button', { name: /登录/i })).toBeInTheDocument();
});
  • 这个测试用例检查 Wjllogin 组件是否能够正确渲染,并且确保特定的文本和登录按钮存在于文档中。
it('successful login', async () => {
  // Mock the API response for a successful login
  wjllogin.mockResolvedValueOnce({
    data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },
  });

  render(
    <MemoryRouter>
      <Wjllogin />
    </MemoryRouter>
  );

  // Fill in the input fields
  fireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });
  fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });
  fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });

  // Click the login button
  fireEvent.click(screen.getByRole('button', { name: /登录/i }));

  // Wait for the success message to appear
  await waitFor(() => {
    expect(screen.getByText(/登录成功, 即将跳转至主页/)).toBeInTheDocument();
  });

  // Check if sessionStorage is set (you may need to mock sessionStorage)
  expect(sessionStorage.getItem('sid')).toBe('123');
  expect(sessionStorage.getItem('clazz')).toBe('A1');
  expect(sessionStorage.getItem('sname')).toBe('John Doe');
});
  • 这个测试用例模拟了一个成功的登录过程。它首先设置了 wjllogin 函数的返回值,然后渲染组件,填写表单,点击登录按钮,并最终检查成功消息是否出现以及 sessionStorage 是否正确设置。
it('failed login', async () => {
  // Mock the API response for a failed login
  wjllogin.mockResolvedValueOnce({
    data: { code: 400 },
  });

  render(
    <MemoryRouter>
      <Wjllogin />
    </MemoryRouter>
  );

  // Fill in the input fields
  fireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });
  fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });
  fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });

  // Click the login button
  fireEvent.click(screen.getByRole('button', { name: /登录/i }));

  // Wait for the error message to appear
  await waitFor(() => {
    expect(screen.getByText(/登录失败,请检查姓名、身份证号或学号/)).toBeInTheDocument();
  });
});
  • 这个测试用例模拟了一个失败的登录过程。它设置了 wjllogin 函数的返回值为一个错误代码,然后填写不正确的表单数据,点击登录按钮,并最终检查错误消息是否出现。

这段代码为 Wjllogin 组件提供了全面的测试,包括组件的渲染、成功登录和失败登录的场景。通过使用 Vitest 和 React Testing Library,测试用例能够模拟用户交互并验证组件的行为是否符合预期。 

6、运行测试

通过在终端运行命令 npx vitser 看结果

npx vitest

这里三个测试用例全部通过,说明代码编写没有问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/923644.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探秘嵌入式位运算:基础与高级技巧

目录 一、位运算基础知识 1.1. 位运算符 1.1.1. 与运算&#xff08;&&#xff09; 1.1.2. 或运算&#xff08;|&#xff09; 1.1.3. 异或运算&#xff08;^&#xff09; 1.1.4. 取反运算&#xff08;~&#xff09; 1.1.5. 双重按位取反运算符&#xff08;~~&#xf…

SpringBoot - 优雅的实现【账号登录错误次数的限制和锁定】

文章目录 Pre需求实现步骤简易实现1. 添加依赖2. 配置文件3. 自定义注解4. AOP切面5. 使用自定义注解&#xff1a;6. 测试 附总结 Pre SpringBoot - 优雅的实现【流控】 需求 需求描述&#xff1a; 登录错误次数限制&#xff1a;在用户登录时&#xff0c;记录每个账号的登录错…

SRIO DRP动态速率配置说明(详细讲解)

目录 一、SRIO IP时钟结构 1、时钟内部结构 2、时钟直接的关系 3、时钟计算原理 ​二、SRIO DRP介绍 ​1、MMCM DRP配置(xapp888) 2、CPLL DRP配置(ug476) 关于CPLL DRP配置详细介绍&#xff1a; GTX中CPLL、QPLL DRP动态配置方法&#xff08;详解&#xff09;-CSDN博客…

动态规划之背包问题

0/1背包问题 1.二维数组解法 题目描述&#xff1a;有一个容量为m的背包&#xff0c;还有n个物品&#xff0c;他们的重量分别为w1、w2、w3.....wn&#xff0c;他们的价值分别为v1、v2、v3......vn。每个物品只能使用一次&#xff0c;求可以放进背包物品的最大价值。 输入样例…

推荐一款龙迅HDMI2.0转LVDS芯片 LT6211UX LT6211UXC

龙迅的HDMI2.0转LVDS芯片LT6211UX和LT6211UXC是两款高性能的转换器芯片&#xff0c;它们在功能和应用上有所差异&#xff0c;同时也存在一些共同点。以下是对这两款芯片的详细比较和分析&#xff1a; 一、LT6211UX 主要特性&#xff1a; HDMI2.0至LVDS和MIPI转换器。HDMI2.0输…

深度学习模型:循环神经网络(RNN)

一、引言 在深度学习的浩瀚海洋里&#xff0c;循环神经网络&#xff08;RNN&#xff09;宛如一颗独特的明珠&#xff0c;专门用于剖析序列数据&#xff0c;如文本、语音、时间序列等。无论是预测股票走势&#xff0c;还是理解自然语言&#xff0c;RNN 都发挥着举足轻重的作用。…

[STM32]从零开始的STM32 FreeRTOS移植教程

一、前言 如果能看到这个教程的话&#xff0c;说明大家已经学习嵌入式有一段时间了。还记得嵌入式在大多数时候指的是什么吗&#xff1f;是的&#xff0c;我们所说的学习嵌入式大部分时候都是在学习嵌入式操作系统。从简单的一些任务状态机再到复杂一些的RTOS&#xff0c;再到最…

《操作系统 - 清华大学》5 -4:虚拟技术

文章目录 0. 虚拟存储的定义1. 目标2.局部性原理3. 虚拟存储的思路与规则4. 虚拟存储的基本特征5. 虚拟页式存储管理5.1 页表表项5.2 示例 0. 虚拟存储的定义 1. 目标 虚拟内存管理技术&#xff0c;简称虚存技术。那为什么要虚存技术&#xff1f;在于前面覆盖和交换技术&#…

2024APMCM亚太杯数学建模C题【宠物行业】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年APMCM亚太地区大学生数学建模竞赛C题的成品论文。 给大家看一下目录吧&#xff1a; 目录 摘 要&#xff1a; 10 一、问题重述 14 二&#xff0e;问题分析 15 2.1问题一 15 2.2问题二 15 2.3问题三…

YOLOv8模型pytorch格式转为onnx格式

一、YOLOv8的Pytorch网络结构 model DetectionModel((model): Sequential((0): Conv((conv): Conv2d(3, 64, kernel_size(3, 3), stride(2, 2), padding(1, 1))(act): SiLU(inplaceTrue))(1): Conv((conv): Conv2d(64, 128, kernel_size(3, 3), stride(2, 2), padding(1, 1))(a…

零基础3分钟快速掌握 ——Linux【终端操作】及【常用指令】Ubuntu

1.为啥使用Linux做嵌入式开发 能广泛支持硬件 内核比较高效稳定 原码开放、软件丰富 能够完善网络通信与文件管理机制 优秀的开发工具 2.什么是Ubuntu 是一个以桌面应用为主的Linux的操作系统&#xff0c; 内核是Linux操作系统&#xff0c; 具有Ubuntu特色的可视…

VScode 连不上远程云服务器

今天下午写代码&#xff0c;打开 VScode 突然发现连不上云服务器了&#xff0c;一开始以为自己密码输错了&#xff0c;试了好多次&#xff0c;依然是这样的 经过查资料发现&#xff0c;应该是版本的自动升级导致的&#xff01;解决方案如下&#xff1a; 1、删除 windows 端的 …

图像分割——区域增长

一 区域增长 图像灰度阈值分割技术都没有考虑到图像像素空间的连通性。区域增长法则正好相反,顾及像素的连接性. 方法&#xff1a;1&#xff09;选择一个或一组种子&#xff1b; 2&#xff09;选择特征及相似性判决准则&#xff1b; 3&#xff09;从该种子开始向外生长&#x…

音视频相关的一些基本概念

音视频相关的一些基本概念 文章目录 音视频相关的一些基本概念RTTH264profile & levelI帧 vs IDRMP4 封装格式AAC封装格式TS封装格式Reference RTT TCP中的RTT指的是“往返时延”&#xff08;Round-Trip Time&#xff09;&#xff0c;即从发送方发送数据开始&#xff0c;到…

春秋云境 CVE 复现

CVE-2022-4230 靶标介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (admin) 的用户可以使用受影响的功能&#xff0c;但是该插件有一个设置允许低权限用…

Linux—进程概念学习-03

目录 Linux—进程学习—31.进程优先级1.1Linux中的进程优先级1.2修改进程优先级—top 2.进程的其他概念3.进程切换4.环境变量4.0环境变量的理解4.1环境变量的基本概念4.2添加环境变量—export4.3Linux中环境变量的由来4.4常见环境变量4.5和环境变量相关的命令4.6通过系统调用获…

go语言逆向-基础basic

文章目录 go 编译命令 ldflags -w -s的作用和问题使用 file 命令查看文件类型 go 语言逆向参考go ID版本GOROOT和GOPATHGOROOTGOPATHGOROOT和GOPATH的关系示例 go build和 go modpclntab &#xff08;Program Counter Line Table 程序计数器行数映射表&#xff09;Moduledata程…

RAG架构类型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

PostgreSQL详细安装教程

#安装PostgreSQL的yum仓库 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm#安装PostgreSQL 15版本 sudo yum install -y postgresql15-server#初始化数据库&#xff08;若要自定义数据库存储目录…

uniapp介入极光推送教程 超级详细

直接按照下面教程操作 一步一步来 很快就能 完成 下面的文章非常详细 &#xff0c;我就不班门弄斧了 直接上原文链接 https://blog.csdn.net/weixin_52830464/article/details/143823231