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

如何使用Jest模拟Recoil Selector以测试React组件?

解决Recoil Selector的Jest模拟问题

不需要重构组件,以下几种方法可以直接实现isAuthenticated selector的状态模拟,覆盖认证真假两种测试场景:

方法一:直接模拟useRecoilValue的返回值

通过Jest mock recoil模块中的useRecoilValue函数,精准控制传入isAuthenticated时的返回值:

import { render, screen } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { CreateMenu } from './CreateMenu';
import { useRecoilValue } from 'recoil';
import { isAuthenticated } from 'authentication';

// 保留recoil的其他原生导出,仅mock useRecoilValue
jest.mock('recoil', () => ({
  ...jest.requireActual('recoil'),
  useRecoilValue: jest.fn(),
}));

test('未认证时不显示认证提示', () => {
  // 设置useRecoilValue返回未认证状态
  (useRecoilValue as jest.Mock).mockReturnValue(false);
  
  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.queryByText("hey you're authenticated")).not.toBeInTheDocument();
});

test('认证时显示认证提示', () => {
  // 设置useRecoilValue返回已认证状态
  (useRecoilValue as jest.Mock).mockReturnValue(true);
  
  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.getByText("hey you're authenticated")).toBeInTheDocument();
});

如果组件中有多个useRecoilValue调用,可以根据传入的selector参数区分返回值:

(useRecoilValue as jest.Mock).mockImplementation((selector) => {
  if (selector === isAuthenticated) {
    return true; // 仅针对isAuthenticated返回目标值
  }
  // 其他selector调用原生方法
  return jest.requireActual('recoil').useRecoilValue(selector);
});

方法二:模拟authentication模块的isAuthenticated导出

直接mock模块导出的selector,让它返回符合Recoil规范的自定义结构:

import { render, screen } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { CreateMenu } from './CreateMenu';
import { isAuthenticated } from 'authentication';

// mock authentication模块,保留其他导出仅覆盖isAuthenticated
jest.mock('authentication', () => ({
  ...jest.requireActual('authentication'),
  isAuthenticated: jest.fn(),
}));

// 每个测试用例前重置mock结构
beforeEach(() => {
  (isAuthenticated as jest.Mock).mockReturnValue({
    key: 'authselector',
    get: () => false,
  });
});

test('未认证时不显示认证提示', () => {
  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.queryByText("hey you're authenticated")).not.toBeInTheDocument();
});

test('认证时显示认证提示', () => {
  // 修改mock的selector返回已认证状态
  (isAuthenticated as jest.Mock).mockReturnValue({
    key: 'authselector',
    get: () => true,
  });
  
  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.getByText("hey you're authenticated")).toBeInTheDocument();
});

方法三:使用Recoil官方测试工具

借助recoil-testing-library覆盖selector的返回值,无需mock函数,更贴合Recoil测试生态:

  1. 先安装依赖:
npm install --save-dev recoil-testing-library
  1. 测试代码:
import { render, screen } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { CreateMenu } from './CreateMenu';
import { isAuthenticated } from 'authentication';
import { act, RecoilTestRenderer } from 'recoil-testing-library';

test('未认证时不显示认证提示', async () => {
  const { renderHook } = RecoilTestRenderer();
  
  // 覆盖isAuthenticated的返回值为false
  await act(async () => {
    renderHook(() => useRecoilValue(isAuthenticated), {
      overrides: [[isAuthenticated, false]],
    });
  });

  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.queryByText("hey you're authenticated")).not.toBeInTheDocument();
});

test('认证时显示认证提示', async () => {
  const { renderHook } = RecoilTestRenderer();
  
  // 覆盖isAuthenticated的返回值为true
  await act(async () => {
    renderHook(() => useRecoilValue(isAuthenticated), {
      overrides: [[isAuthenticated, true]],
    });
  });

  render(
    <RecoilRoot>
      <CreateMenu />
    </RecoilRoot>
  );
  
  expect(screen.getByText("hey you're authenticated")).toBeInTheDocument();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 02:17:56