如何使用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测试生态:
- 先安装依赖:
npm install --save-dev recoil-testing-library
- 测试代码:
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
相关产品推荐
相关产品推荐

