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
相关产品推荐
相关产品推荐

