基于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 和更新函数 setNamelet [card, setCard] = useState(""); // 定义状态变量 card 和更新函数 setCardlet [tid, setTid] = useState(""); // 定义状态变量 tid 和更新函数 setTidconst [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) {// 登录成功,保存用户信息到 sessionStoragesessionStorage.setItem("sid", sid); // 保存会话 IDsessionStorage.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">{" "}{/* 顶部区域 */}<imgclassName="main"src="https://cdn7.axureshop.com/demo/2001850/images/%E5%9C%A8%E7%BA%BF%E8%80%83%E8%AF%95/u2853.svg"alt=""/>{" "}{/* 主图标 */}<imgclassName="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><inputid="name"value={name}onChange={(e) => setName(e.target.value)}/></p><p><label htmlFor="card">身份证号:</label><inputid="card"type="text" // 输入框类型value={card} // 绑定到 card 状态onChange={(e) => {setCard(e.target.value); // 更新 card 状态}}/></p><p><label htmlFor="tid">学号:</label> {/* 学号标签 */}<inputid="tid"type="text" // 输入框类型value={tid} // 绑定到 tid 状态onChange={(e) => {setTid(e.target.value); // 更新 tid 状态}}/></p><p><buttonclassName="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(() => {// 清除所有的 mocksvi.clearAllMocks();});it('renders Wjllogin component', () => {render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Check if elements are renderedexpect(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 loginwjllogin.mockResolvedValueOnce({data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the success message to appearawait 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 loginwjllogin.mockResolvedValueOnce({data: { code: 400 },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the error message to appearawait 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 renderedexpect(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 loginwjllogin.mockResolvedValueOnce({data: { code: 200, sid: '123', clazz: 'A1', sname: 'John Doe' },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'John Doe' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '123456789012345678' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '2023001' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the success message to appearawait 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 loginwjllogin.mockResolvedValueOnce({data: { code: 400 },});render(<MemoryRouter><Wjllogin /></MemoryRouter>);// Fill in the input fieldsfireEvent.change(screen.getByLabelText(/考生姓名:/), { target: { value: 'Invalid User' } });fireEvent.change(screen.getByLabelText(/身份证号:/), { target: { value: '000000000000000000' } });fireEvent.change(screen.getByLabelText(/学号:/), { target: { value: '0000000' } });// Click the login buttonfireEvent.click(screen.getByRole('button', { name: /登录/i }));// Wait for the error message to appearawait waitFor(() => {expect(screen.getByText(/登录失败,请检查姓名、身份证号或学号/)).toBeInTheDocument();});
});
  • 这个测试用例模拟了一个失败的登录过程。它设置了 wjllogin 函数的返回值为一个错误代码,然后填写不正确的表单数据,点击登录按钮,并最终检查错误消息是否出现。

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

6、运行测试

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

npx vitest

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何用通义灵码助力项目开发 | OceanBase obdiag 项目共建实践

本文来自 obdiag 项目共建的用户分享 一、背景 我的数据库探索之旅始于OceanBase。作为一位满怀好奇心的DBA&#xff0c;我内心始终怀揣着对数据库内部运作机制的无尽向往。开源如同一把钥匙&#xff0c;为我们这些求知欲旺盛的“好奇猫”解锁了通往新知的神秘大门。在众多分布…

idea_卸载与安装

卸载与安装 卸载1、设置 -> 应用2、查找到应用&#xff0c;点击卸载3、把删除记录和设置都勾选上4、删除其它几个位置的残留 安装1、下载安装包2、欢迎安装 -> Next3、选择安装目录 -> Next4、创建快捷图标和添加到环境变量5、确认文件夹的名称 -> Install6、完成安…

day01

Hm-Footer.vue <template><div class"hm-footer">我是hm-footer</div></template><script>export default {}</script><style>.hm-footer{height:100px;line-height:100px;text-align:center;font-size:30px;background-…

NLP 1、人工智能与NLP简介

人人都不看好你&#xff0c;可偏偏你最争气 —— 24.11.26 一、AI和NLP的基本介绍 1.人工智能发展流程 弱人工智能 ——> 强人工智能 ——> 超人工智能 ① 弱人工智能 人工智能算法只能在限定领域解决特定的问题 eg&#xff1a;特定场景下的文本分类、垂直领域下的对…

基于混合ABC和A*算法复现

基于混合ABC和A*算法复现 一、背景介绍二、算法原理&#xff08;一&#xff09;A*算法原理&#xff08;二&#xff09;人工蜂群算法原理&#xff08;三&#xff09;混合ABC和A*算法策略 三、代码实现&#xff08;一&#xff09;数据准备&#xff08;二&#xff09;关键函数实现…

解决SpringBoot连接Websocket报:请求路径 404 No static resource websocket.

问题发现 最近在工作中用到了WebSocket进行前后端的消息通信&#xff0c;后端代码编写完后&#xff0c;测试一下是否连接成功&#xff0c;发现报No static resource websocket.&#xff0c;看这个错貌似将接口变成了静态资源来访问了&#xff0c;第一时间觉得是端点没有注册成…

VITE+VUE3+TS环境搭建

前言&#xff08;与搭建项目无关&#xff09;&#xff1a; 可以安装一个node管理工具&#xff0c;比如nvm&#xff0c;这样可以顺畅的切换vue2和vue3项目&#xff0c;以免出现项目跑不起来的窘境。我使用的nvm&#xff0c;当前node 22.11.0 目录 搭建项目 添加状态管理库&…

红外小目标检测

目录 背景概述算法原理演示效果核心逻辑 使用方式基础镜像配置环境直接运行 参考文献 文章声明&#xff0c;非广告&#xff0c;仅个人体验。 背景 红外图像在许多领域中都有所应用。例如军事领域中&#xff0c;经常需要通过红外成像设备对远距离的目标进行侦察和监视&#xff…

【滑动窗口】找到字符串中所有字母异位词

文章目录 找到字符串中所有字母异位词 class Solution { public:vector<int> findAnagrams(string s, string p) {vector<int> ret;int sLen s.size(), pLen p.size(), validChar;// 母串长度比子串长度还小 直接返回空vectorif (sLen < pLen)return ret;// …

nodepad配置c/c++ cmd快速打开创建项目文件

前提:下载MinGw,并且配置环境变量 点击阅读次篇文章配置MinGw 无论是哪个编译器&#xff0c;执行c文件都是经历以下步骤: 编译文件生成exe文件执行该exe文件 我们先手动完成这两部 手动编译文件使用指令 gcc {你的c文件} -o {生成文件名}生成exe文件 第二步运行exe直接点击该文…

Opencv+ROS实现颜色识别应用

目录 一、工具 二、原理 概念 本质 三、实践 添加发布话题 主要代码 四、成果 五、总结 一、工具 opencvros ubuntu18.04 摄像头 二、原理 概念 彩色图像&#xff1a;RGB&#xff08;红&#xff0c;绿&#xff0c;蓝&#xff09; HSV图像&#xff1a;H&#xff0…

Vue.Draggable使用nested-with-vmodel进行拖拽

Vue.Draggable使用nested-with-vmodel进行拖拽 1. 介绍 ‌draggable‌是一个基于Sortable.js的Vue组件&#xff0c;用于实现拖拽功能。它支持触摸设备、拖拽和选择文本、智能滚动、不同列表之间的拖拽等功能&#xff0c;并且与Vue的视图模型同步刷新&#xff0c;兼容Vue2的过…

【湿度数据处理】中国地面气候资料日值数据集(V3.0)(MATLAB全代码)

【湿度数据处理】中国地面气候资料日值数据集 处理1:数据范围筛选处理2:缺测数据筛查处理3:缺测数据插补参考基于此博客完成各要素数据提取后-【数据集处理】中国地面气候资料日值数据集(V3.0)(含MATLAB全代码),进行后续数据筛选及缺测处理,此处以湿度数据为例。 提取到的…

vulnhub靶场之corrosion靶场1

corrosion靶场1 前言 靶机&#xff1a;corrosion靶场1 攻击&#xff1a;kali 主机发现 使用arp-scan -l发现主机IP&#xff0c;这里直接查看虚拟机需要登录&#xff0c;不过官方并没有提供密码&#xff0c;所以&#xff0c;扫描出IP地址 信息收集 使用nmap查看端口及服务…

代码随想录算法训练营day46|动态规划09

买卖股票的最佳时机四 之前是最多只能完成两笔交易&#xff0c;现在是至多可以买卖k次&#xff0c;那么状态数需要定为2*k1种&#xff0c;此时&#xff0c;就要分析多种情况的递推式 找到奇偶数交替的规则即可 class Solution { public:int maxProfit(int k, vector<int&g…

前端-Git

一.基本概念 Git版本控制系统时一个分布式系统&#xff0c;是用来保存工程源代码历史状态的命令行工具 简单来说Git的作用就是版本管理工具。 Git的应用场景&#xff1a;多人开发管理代码&#xff1b;异地开发&#xff0c;版本管理&#xff0c;版本回滚。 Git 的三个区域&a…

【软件介绍】变声工具RVC本地部署使用方法

RVC&#xff08;Real-Time Voice Conversion&#xff09;软件是一种能够实现实时声音转换的技术工具&#xff0c;它允许用户改变自己或他人的语音特征&#xff0c;比如音调、音色等&#xff0c;以达到变声的效果。这种技术在娱乐、游戏、内容创作等领域有着广泛的应用。下面是一…

IntelliJ IDEA 中,自动导包功能

在 IntelliJ IDEA 中&#xff0c;自动导包功能可以极大地提高开发效率&#xff0c;减少手动导入包所带来的繁琐和错误。以下是如何在 IntelliJ IDEA 中设置和使用自动导包功能的详细步骤&#xff1a; 一、设置自动导包 打开 IntelliJ IDEA&#xff1a; 启动 IntelliJ IDEA 并打…

【CANOE】【Capl】【RS232】控制串口设备

系列文章目录 内置函数&#xff0c;来控制传统的串口设备&#xff0c;比如继电器等 文章目录 系列文章目录前言一、控制串口二、自定义相关的参数RS232Configure**函数语法****函数功能****参数说明****返回值****示例代码** 三、回调函数的使用RS232OnSend**函数语法****函数…

AX58100+STM32使用FSMC接口,运行EtherCAT Slave协议栈

目录 简介环境硬件接线MCU一侧的初始化时钟FSMC外部中断timer 协议栈生成EtherCAT SlaveSlave infomationgenerichardwareEtherCAT State MachineSynchronisationApplicaitonProcessDataMailbox OD TOOL 协议栈移植协议栈集成和错误初步解决启动协议栈 应用开发集成到TWINCAT集…