React导出箭头函数用于Jest单元测试遇checkIfBlank未定义问题
解决Jest测试中React组件内部函数未定义的问题
问题背景
在对Register组件进行Jest单元测试时,始终遇到checkIfBlank函数未定义的错误。该函数原本是组件内部的箭头函数,直接尝试导出内部函数无法生效——因为它处于组件函数的闭包范围内,外部无法访问。
解决方案
核心是将checkIfBlank重构为纯函数,脱离组件内部的state依赖,改为通过参数接收所需数据和方法,再导出该函数供测试调用,同时在组件内调用时传入对应参数。
步骤1:重构并导出checkIfBlank
将checkIfBlank移到Register组件外部,改为接收参数的纯函数,并添加导出语句:
import React from "react"; import axios from 'axios'; import bycrypt from 'bcryptjs'; import { Link } from "react-router-dom"; import { Helmet } from "react-helmet"; import { useState } from "react"; import InputBoxForInfo from "../components/input-box-for-info"; import Button from "../components/button"; import "./register.css"; // 移到组件外部,作为独立纯函数并导出 export const checkIfBlank = (name, surname, email, username, password, setErrorMessage, checkIfUserExists) => { const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const passwordPattern = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/; // 字段为空时提示 if (!name || !surname || !email || !username || !password) { alert("Please enter all details"); return; } // 验证邮箱和密码格式 if (emailPattern.test(email) && passwordPattern.test(password)) { checkIfUserExists(); } else { if (!emailPattern.test(email)) { setErrorMessage('Please enter a valid email'); } if (!passwordPattern.test(password)) { setErrorMessage("Please enter a stronger password"); } } }; const Register = (props) => { const [name, setName] = useState(""); const [surname, setSurname] = useState(""); const [email, setEmail] = useState(""); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const salt = bycrypt.genSaltSync(10); const hashedPassword = bycrypt.hashSync(password,salt); const[exist, setExistence] = useState(""); const [errorMessage, setErrorMessage] = useState(''); const postDetails = () =>{ axios.post("http://localhost:3002/api/post/register",{name:name, surname:surname, email: email, username: username, password: hashedPassword}); setTimeout(function(){ window.location.href = 'http://localhost:3000/login'; }, 1000); } const checkIfUserExists = () =>{ axios .get("http://localhost:3002/api/get/doesExist/" + username) .then(function(response){ const userExists = response.data; console.log(response.data); if (JSON.stringify(userExists) == "[]"){ setErrorMessage('Account created successfully'); postDetails(); } else{ setErrorMessage('Username already exists'); } }); } const handleRegister = () => { console.log( `Name: ${name}, Surname: ${surname} Email: ${email}, Username: ${username}, Password: ${hashedPassword}` ); // 调用重构后的函数,传入所需参数 checkIfBlank(name, surname, email, username, password, setErrorMessage, checkIfUserExists); }; // 组件return部分保持不变 return ( <div className="register-container"> <Helmet> <title>register - Project ARENA</title> <meta property="og:title" content="register - Project ARENA" /> </Helmet> <div className="register-container1"> <Link to="/" className="register-navlink"> <svg viewBox="0 0 877.7142857142857 1024" className="register-backbtn" > <path d="M519.429 797.143l58.286-58.286c14.286-14.286 14.286-37.143 0-51.429l-175.429-175.429 175.429-175.429c14.286-14.286 14.286-37.143 0-51.429l-58.286-58.286c-14.286-14.286-37.143-14.286-51.429 0l-259.429 259.429c-14.286 14.286-14.286 37.143 0 51.429l259.429 259.429c14.286 14.286 37.143 14.286 51.429 0zM877.714 512c0 242.286-196.571 438.857-438.857 438.857s-438.857-196.571-438.857-438.857 196.571-438.857 438.857-438.857 438.857 196.571 438.857 438.857z"></path> </svg> </Link> </div> <div className="register-container5"> <span className="register-text"> <span>Register</span> <br></br> </span> <InputBoxForInfo buttonText="NAME" onChange={(e) => setName(e.target.value)} rootClassName="input-box-for-info-root-class-name" ></InputBoxForInfo> <InputBoxForInfo buttonText="SURNAME" onChange={(e) => setSurname(e.target.value)} rootClassName="input-box-for-info-root-class-name2" ></InputBoxForInfo> <InputBoxForInfo buttonText="EMAIL" onChange={(e) => setEmail(e.target.value)} rootClassName="input-box-for-info-root-class-name3" ></InputBoxForInfo> <InputBoxForInfo buttonText="USERNAME" onChange={(e) => setUsername(e.target.value)} rootClassName="input-box-for-info-root-class-name4" ></InputBoxForInfo> <InputBoxForInfo buttonText="PASSWORD" onChange={(e) => setPassword(e.target.value)} isPassword rootClassName="input-box-for-info-root-class-name5" ></InputBoxForInfo> <br></br> <Button name="Register" onClick={() => { console.log("Register button clicked"); handleRegister(); }} rootClassName="button-root-class-name" ></Button> <br></br> {errorMessage && <div className="error">{errorMessage}</div>} <span className="register-text3">Already have an account?</span> <Link to="/login" className="register-navlink1 button"> Login here </Link> </div> </div> ); }; export default Register;
步骤2:调整Jest测试代码
现在checkIfBlank是纯函数,测试时需要传入模拟的checkIfUserExists方法(原测试遗漏了该参数),同时模拟alert方法:
import React from 'react'; import Register from './register'; import { checkIfBlank } from './register.js'; // 测试套件 describe('checkIfBlank', () => { let setErrorMessage = jest.fn(); let checkIfUserExists = jest.fn(); // 测试有效输入 test('valid input triggers checkIfUserExists', () => { const name = 'John'; const surname = 'Doe'; const email = 'john.doe@example.com'; const username = 'johndoe'; const password = 'Passw0rd'; checkIfBlank(name, surname, email, username, password, setErrorMessage, checkIfUserExists); expect(setErrorMessage).not.toHaveBeenCalled(); expect(checkIfUserExists).toHaveBeenCalled(); }); // 测试字段为空 test('missing field shows alert', () => { // 模拟alert方法 window.alert = jest.fn(); const name = ''; const surname = 'Doe'; const email = 'john.doe@example.com'; const username = 'johndoe'; const password = 'Passw0rd'; checkIfBlank(name, surname, email, username, password, setErrorMessage, checkIfUserExists); expect(window.alert).toHaveBeenCalledWith('Please enter all details'); expect(setErrorMessage).not.toHaveBeenCalled(); expect(checkIfUserExists).not.toHaveBeenCalled(); }); // 测试无效邮箱 test('invalid email sets error message', () => { const name = 'John'; const surname = 'Doe'; const email = 'johndoe@examplecom'; const username = 'johndoe'; const password = 'Passw0rd'; checkIfBlank(name, surname, email, username, password, setErrorMessage, checkIfUserExists); expect(setErrorMessage).toHaveBeenCalledWith('Please enter a valid email'); expect(checkIfUserExists).not.toHaveBeenCalled(); }); // 测试弱密码 test('weak password sets error message', () => { const name = 'John'; const surname = 'Doe'; const email = 'john.doe@example.com'; const username = 'johndoe'; const password = 'password'; checkIfBlank(name, surname, email, username, password, setErrorMessage, checkIfUserExists); expect(setErrorMessage).toHaveBeenCalledWith('Please enter a stronger password'); expect(checkIfUserExists).not.toHaveBeenCalled(); }); });
为什么之前的导出方法无效
之前尝试的导出方式(如在组件内部写export const checkIfBlank、export {checkIfBlank}等)无法生效,因为原函数定义在Register组件函数的内部,属于组件闭包的私有变量,外部模块无法直接访问。只有将函数移到组件外部,成为独立的模块级函数,才能成功导出并被测试代码引用。
内容的提问来源于stack exchange,提问作者Mu'aaz Bassa
相关产品推荐
相关产品推荐

