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

React Jest单元测试如何正确Mock UserService服务

问题描述

我正在寻找mock名为UserService的keycloak服务的方法,实际仅需要模拟isLoggedIn和登录相关函数即可。我本以为这并非复杂任务,但经过数小时尝试,始终无法得到可行的解决方案。

代码示例

UserService.js

import Keycloak from "keycloak-js";

const _kc = new Keycloak({
    url: "http://localhost:80",
    realm: "realmName",
    clientId: "clientName"
});

const initKeycloak = (onAuthenticatedCallback, onNotAuthenticatedCallback) => {
    _kc.init(
        {
            onLoad: 'check-sso',
            silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
            pkceMethod: 'S256',
            checkLoginIframe: false
        }
    )
        .then((authenticated) => {
            if (!authenticated) {
                console.error("user is not authenticated..!");
            }
            onAuthenticatedCallback();
        })
        .catch((error) => {
            onNotAuthenticatedCallback();
        })
};
const doLogin = _kc.login;

const doLogout = _kc.logout;

const getToken = () => {
    return _kc.token;
}

const isLoggedIn = () => {
    if (_kc.token) {
        return true;
    } else {
        return false;
    }
}

const updateToken = (successCallback) => {
    _kc.updateToken(5)
        .then(successCallback)
        .catch(doLogin);
}

const getUsername = () => _kc.tokenParsed?.preferred_username;

const hasRole = (roles) => {
    return roles.some((role) => _kc.hasRealmRole(role));
}

const UserService = {
    initKeycloak,
    doLogin,
    doLogout,
    isLoggedIn,
    getToken,
    updateToken,
    getUsername,
    hasRole,
};

export default UserService;

我已经尝试使用jest.mock()函数实现mock,目前点击登录按钮已不会触发报错,但添加按钮始终无法激活。

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import OrganizationsPage from "./OrganizationsPage";
import { act } from "react-test-renderer";

jest.mock("../Services/UserService", () => ({
    ...jest.requireActual("../Services/UserService"),
    doLogin: jest.fn(() => { return true }),
    isLoggedIn: jest.fn(() => { return true }),
}));

const MockedOrganizationsPage = () => {
    return (
        <BrowserRouter>
            <OrganizationsPage />
        </BrowserRouter>
    );
}

describe('OrganizationsPage Alter', () => {
    it('should activate alter buttons on login', async () => {

        // console.log(UserService.isLoggedIn())
        await act(async () => render(<MockedOrganizationsPage />))

        const loginButton = screen.getByText("Login");
        userEvent.click(loginButton);
        const addButton = screen.getByText("Add Organization");
        userEvent.click(addButton);
    })
})

解决方案

问题根因

现有mock逻辑存在两个问题导致按钮状态异常:

  • 用jest.requireActual引入了真实UserService的剩余逻辑,模块内部的真实Keycloak实例_kc会正常初始化,如果组件逻辑直接读取_kc.token判断权限,不会走你mock的isLoggedIn方法,初始状态始终判定为未登录。
  • userEvent.click是异步操作,测试代码中没有加await等待DOM更新,点击登录后组件状态还未完成重渲染就执行后续逻辑,无法拿到激活状态的添加按钮;同时mock的doLogin没有同步触发登录状态变更,组件感知不到登录动作完成。

修复代码

不要引入真实模块逻辑,直接完全mock整个UserService,所有交互操作都加await等待渲染完成,参考写法如下:

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { BrowserRouter } from "react-router-dom";
import OrganizationsPage from "./OrganizationsPage";
import UserService from "../Services/UserService";

// 完全mock UserService模块,屏蔽真实Keycloak初始化逻辑
jest.mock("../Services/UserService", () => ({
  __esModule: true,
  default: {
    initKeycloak: jest.fn((onSuccess) => onSuccess()),
    doLogin: jest.fn(),
    doLogout: jest.fn(),
    isLoggedIn: jest.fn(() => false), // 初始状态返回未登录
    getToken: jest.fn(() => "mock-test-token"),
    updateToken: jest.fn((cb) => cb()),
    getUsername: jest.fn(() => "test_user"),
    hasRole: jest.fn(() => true),
  }
}));

const MockedOrganizationsPage = () => {
  return (
    <BrowserRouter>
      <OrganizationsPage />
    </BrowserRouter>
  );
}

describe('OrganizationsPage Alter', () => {
  const user = userEvent.setup();

  it('should activate alter buttons on login', async () => {
    render(<MockedOrganizationsPage />);

    // 校验初始状态
    const loginButton = screen.getByText("Login");
    expect(loginButton).toBeInTheDocument();

    // 点击登录,更新mock返回值为已登录
    await user.click(loginButton);
    UserService.isLoggedIn.mockReturnValue(true);

    // 等待组件重渲染后校验按钮状态
    const addButton = await screen.findByText("Add Organization");
    expect(addButton).not.toBeDisabled();
    await user.click(addButton);
  })
})

如果组件通过Keycloak回调函数触发状态更新,直接在doLogin的mock实现里调用对应回调即可同步触发视图刷新。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:48:11