如何为React类组件login方法中的fetch编写Jest mock函数?
用Jest Mock Fetch并测试React登录组件的完整方案
找了好久没找到可行的方案?别着急,我来一步步帮你实现针对这个Login组件的测试,覆盖登录成功和失败的核心场景。
第一步:全局Mock Fetch函数
你的组件直接使用了浏览器全局的fetch,在Jest测试环境中,我们需要先mock这个全局方法,才能模拟接口返回的不同场景。可以用jest.spyOn来临时替换global.fetch,测试完成后再恢复原始实现。
第二步:编写组件集成测试(推荐)
我们用@testing-library/react模拟真实用户交互,这样测试更贴近实际使用场景。先确保你已经安装了必要依赖:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest
测试登录成功场景
这个用例会模拟用户输入正确账号密码、点击登录,验证fetch调用、localStorage存储和Redux状态变化:
import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { Provider } from 'react-redux'; import { store } from '../../store'; import Login from './Login'; describe('Login Component - 登录成功场景', () => { beforeEach(() => { // 每次测试前清空localStorage并重置fetch mock localStorage.clear(); jest.spyOn(global, 'fetch').mockResolvedValue({ text: () => Promise.resolve('fake-jwt-token'), // 模拟返回JWT令牌 }); }); afterEach(() => { // 测试后恢复fetch的原始实现 global.fetch.mockRestore(); }); it('成功登录后应存储token并跳转到搜索页', async () => { // 用Redux Provider包裹组件,确保状态能正常传递 render( <Provider store={store}> <Login /> </Provider> ); // 模拟用户输入账号密码 const emailInput = screen.getByPlaceholderText('Email address'); const passwordInput = screen.getByPlaceholderText('Password'); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'password123' } }); // 模拟点击登录按钮 const loginButton = screen.getByText('Sign in'); fireEvent.click(loginButton); // 验证fetch是否被正确调用 expect(global.fetch).toHaveBeenCalledWith( 'https://reqres.in/api/login', expect.objectContaining({ method: 'POST', headers: expect.any(Headers), body: JSON.stringify({ email: 'test@example.com', password: 'password123', }), }) ); // 等待异步操作完成,检查Redux状态更新 await waitFor(() => { const state = store.getState(); expect(state.login.isLoggedIn).toBe(true); }); // 验证localStorage是否存储了token const storedLogin = localStorage.getItem('login'); expect(JSON.parse(storedLogin)).toEqual({ token: 'fake-jwt-token' }); // 验证是否跳转到搜索页 expect(screen.queryByText('Sign in')).not.toBeInTheDocument(); }); });
测试登录失败场景
这个用例会模拟用户输入错误信息,验证Redux状态设置为未登录、localStorage无存储:
describe('Login Component - 登录失败场景', () => { beforeEach(() => { localStorage.clear(); jest.spyOn(global, 'fetch').mockResolvedValue({ text: () => Promise.resolve('{"error":"user not found"}'), // 模拟返回错误信息 }); }); afterEach(() => { global.fetch.mockRestore(); }); it('登录失败后应设置未登录状态', async () => { render( <Provider store={store}> <Login /> </Provider> ); // 模拟输入错误账号密码 const emailInput = screen.getByPlaceholderText('Email address'); const passwordInput = screen.getByPlaceholderText('Password'); fireEvent.change(emailInput, { target: { value: 'wrong@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'wrongpassword' } }); // 点击登录按钮 const loginButton = screen.getByText('Sign in'); fireEvent.click(loginButton); // 等待异步操作完成,验证状态 await waitFor(() => { const state = store.getState(); expect(state.login.isLoggedIn).toBe(false); }); // 验证localStorage无登录信息 expect(localStorage.getItem('login')).toBeNull(); // 验证登录页面仍存在 expect(screen.getByText('Sign in')).toBeInTheDocument(); }); });
额外:单独测试login类方法(可选)
如果你想单独测试login方法而不通过组件交互,可以直接mock类的原型方法:
import { loginFunc } from './Login'; describe('Login类的login方法', () => { it('应调用fetch并传入正确参数', () => { jest.spyOn(global, 'fetch').mockResolvedValue({ text: () => Promise.resolve('fake-token') }); // 创建mock的上下文对象,模拟组件的this const mockThis = { state: { username: 'test@example.com', password: 'password123' }, props: { dispatch: jest.fn() }, }; // 调用login方法并绑定mock的this loginFunc.call(mockThis); // 验证fetch调用参数 expect(global.fetch).toHaveBeenCalledWith( 'https://reqres.in/api/login', expect.objectContaining({ method: 'POST', body: JSON.stringify({ email: 'test@example.com', password: 'password123' }), }) ); }); });
这样就能完整覆盖你需要的mock fetch和测试场景了,每个用例都验证了关键行为:fetch调用是否正确、状态是否更新、存储是否符合预期。
内容的提问来源于stack exchange,提问作者Hrishikesh D Kakkad
相关产品推荐
相关产品推荐

