You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Testing Library测试React登录组件失败:useState钩子与异步Axios登录函数测试问题求助

解决React登录组件测试失败问题

我来帮你逐一排查并解决测试中的问题,先梳理下你当前遇到的核心错误根源,再给出修正后的完整测试方案:

核心错误分析

  1. 错误的Spy目标:你尝试监听React.useState并命名为signIn,但组件内的signIn是自定义的表单提交函数,两者完全无关,导致toHaveBeenCalledTimes(1)始终返回0。
  2. DOM操作参数错误:userEvent.type('email', 'abc@yahoo.com')中的第一个参数应该是DOM元素,而非字符串,导致输入未触发状态更新。
  3. 异步断言处理不当:未使用waitFor等待Axios请求和状态更新完成,断言过早执行导致失败。
  4. 组件逻辑与测试不匹配:你的Login组件本身不会渲染"welcome"文本,需在父组件Index中验证登录后的页面切换逻辑。

修正后的完整测试代码

测试文件基础配置

/**
 * @jest-environment jsdom
 */
import React from 'react';
import { screen, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Login from "../components/auth/login"
import Index from "../components/index"
import { userContext } from "../utils/context/UserContext"
import axios from 'axios';
import Cookies from 'js-cookie';

// Mock第三方依赖
jest.mock('axios');
jest.mock('js-cookie', () => ({
  get: jest.fn(() => 'mock-csrf-token'),
}));

// 上下文Mock值
const mockSetIsLoggedIn = jest.fn();
const mockSetUser = jest.fn();
const mockUser = {};
const mockIsLoggedIn = false;

describe("Login Component Tests", () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

测试1:验证signIn触发Axios请求及状态更新

test('triggers signIn, calls Axios post, and updates context state on success', async () => {
    // 模拟Axios成功响应
    const mockUserResponse = { user: { name: 'Rony', email: 'rony@example.com' } };
    axios.post.mockResolvedValue({ status: 200, data: mockUserResponse });

    // 渲染Login组件并传入上下文
    render(
      <userContext.Provider value={{ 
        user: mockUser, 
        isLoggedIn: mockIsLoggedIn, 
        setIsLoggedIn: mockSetIsLoggedIn, 
        setUser: mockSetUser 
      }}>
        <Login />
      </userContext.Provider>
    );

    // 获取表单元素
    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const submitButton = screen.getByRole('button', { name: /submit/i });

    // 填写表单并提交
    await userEvent.type(emailInput, 'abc@yahoo.com');
    await userEvent.type(passwordInput, 'abcddef');
    await userEvent.click(submitButton);

    // 验证Axios调用
    expect(axios.post).toHaveBeenCalledTimes(1);
    expect(axios.post).toHaveBeenCalledWith(
      "/api/v1/sessions",
      { user: { email: 'abc@yahoo.com', password: 'abcddef' } },
      expect.objectContaining({ headers: expect.any(Object) })
    );

    // 验证上下文状态更新
    await waitFor(() => {
      expect(mockSetIsLoggedIn).toHaveBeenCalledWith(true);
      expect(mockSetUser).toHaveBeenCalledWith(mockUserResponse.user);
    });
  });

测试2:验证useState值被正确设置

test('updates loginData state when input values change', async () => {
    render(
      <userContext.Provider value={{ 
        user: mockUser, 
        isLoggedIn: mockIsLoggedIn, 
        setIsLoggedIn: mockSetIsLoggedIn, 
        setUser: mockSetUser 
      }}>
        <Login />
      </userContext.Provider>
    );

    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);

    // 验证邮箱输入
    await userEvent.type(emailInput, 'test@example.com');
    expect(emailInput).toHaveValue('test@example.com');

    // 验证密码输入
    await userEvent.type(passwordInput, 'testpassword');
    expect(passwordInput).toHaveValue('testpassword');
  });

测试3:验证登录成功后渲染Welcome文本

注意:需要先在Index组件的根路由添加欢迎文本渲染逻辑,比如:

// Index.js中修改根Route
<Route exact path='/'>
  {isLoggedIn ? <h1>Welcome {user.name}!</h1> : <p>Please login</p>}
</Route>

然后编写测试:

test('renders welcome text after successful login', async () => {
    // 模拟Axios成功响应
    const mockUserResponse = { user: { name: 'Rony', email: 'rony@example.com' } };
    axios.post.mockResolvedValue({ status: 200, data: mockUserResponse });

    // 渲染Index组件(包含路由和上下文)
    render(<Index />);

    // 导航到登录页
    await userEvent.click(screen.getByRole('link', { name: /login/i }));

    // 填写并提交表单
    const emailInput = screen.getByLabelText(/email address/i);
    const passwordInput = screen.getByLabelText(/password/i);
    const submitButton = screen.getByRole('button', { name: /submit/i });

    await userEvent.type(emailInput, 'abc@yahoo.com');
    await userEvent.type(passwordInput, 'abcddef');
    await userEvent.click(submitButton);

    // 等待欢迎文本出现
    await waitFor(() => {
      expect(screen.getByText(/welcome/i)).toBeInTheDocument();
    });
  });
});

额外问题解决:"Unable to find the window object"错误

这个错误通常是JSDOM环境配置或依赖调用时机导致的,按以下方式解决:

  1. 确保测试文件顶部添加了/** @jest-environment jsdom */
  2. 使用jest.mock模拟依赖window的库(如js-cookie)
  3. 避免手动包裹act,React Testing Library的userEvent和render会自动处理异步更新

内容的提问来源于stack exchange,提问作者brg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 08:02:39