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

使用Jest测试Recoil组件时遇<RecoilRoot>报错求助

解决Jest测试Recoil组件时的"This component must be used inside a component"错误

问题根源

你的Account组件用了useRecoilValue(userProfile)获取Recoil状态,但Jest测试里直接渲染组件时没套<RecoilRoot>,Recoil找不到状态上下文就报错了。另外测试代码还有几个逻辑小问题,一起给你修正。


解决步骤

1. 给测试组件套上RecoilRoot

先导入RecoilRoot,渲染Account的时候用它包起来,这样Recoil的状态上下文就有了。如果要模拟userProfile的初始值,还可以结合Recoil的测试工具来mock状态。

先改测试的导入部分:

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import user from "@testing-library/user-event";
import { RecoilRoot } from 'recoil'; // 导入RecoilRoot
import Account from "./index";
import { userProfile } from './你的atom文件路径'; // 导入对应的userProfile atom

然后修改beforeEach里的渲染逻辑:

beforeEach(() => {
    onSubmit.mockClear();
    render(
        <RecoilRoot>
            <Account onSubmit={onSubmit} />
        </RecoilRoot>
    );
});

2. (可选但推荐)模拟Recoil状态

不想依赖真实的userProfile状态的话,用Recoil的测试工具mock一个返回值就行:

// 先导入测试工具
import { mockRecoilValue } from 'recoil/testing';

beforeEach(() => {
    onSubmit.mockClear();
    // mock userProfile的初始值,避免依赖真实状态
    mockRecoilValue(userProfile, { fullname: '测试名称', email: 'test@demo.com' });
    render(
        <RecoilRoot>
            <Account onSubmit={onSubmit} />
        </RecoilRoot>
    );
});

3. 修正测试辅助函数

你写的getName和getEmail只做了点击操作,但没返回输入框元素,导致user.type没法用。改一下,让函数返回对应的输入框:

function getName() {
    const nameInput = screen.getByRole('textbox', { name: /name/i });
    user.click(nameInput);
    return nameInput; // 返回元素给user.type用
}

function getEmail() {
    const emailInput = screen.getByRole('textbox', { name: /email/i });
    user.click(emailInput);
    return emailInput;
}

4. 修复表单提交逻辑

现在的SAVE按钮是type="button",点击它不会触发表单的onSubmit(只有type="submit"的按钮或者手动调用form.submit()才会触发)。有两种解决办法:

  • 要么把按钮的type改成"submit",然后把setIsDoneProfil(true)移到表单提交成功的逻辑里;
  • 要么在测试里手动触发表单提交,或者给按钮的点击事件加个表单提交的逻辑。

比如改组件里的按钮:

<button
    onClick={() => {
        setIsDoneProfil(true);
        document.getElementById('add-form').submit(); // 手动触发表单提交
    }}
    type="button"
    // 其他属性不变
>
    SAVE
</button>

或者在测试里直接触发表单提交:

// 测试用例里,点击按钮后加这行
await user.click(screen.getByRole('button', { name: /save/i }));
await user.submit(screen.getByRole('form', { name: /account/i }));

5. 修正输入框的name属性(笔误)

你组件里的email输入框name写的是"name",和测试里期望的email字段对不上,得改成"email":

<input 
    type="text" 
    name="email" // 把name从"name"改成"email"
    id="email" 
    // 其他属性不变
/>

完整修正后的测试代码

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import user from "@testing-library/user-event";
import { RecoilRoot } from 'recoil';
import { mockRecoilValue } from 'recoil/testing';
import Account from "./index";
import { userProfile } from './你的atom文件路径';

describe('Account', () => {
    const onSubmit = jest.fn();

    beforeEach(() => {
        onSubmit.mockClear();
        mockRecoilValue(userProfile, { fullname: '', email: '' });
        render(
            <RecoilRoot>
                <Account onSubmit={onSubmit} />
            </RecoilRoot>
        );
    });

    it('onSubmit is called when all fields pass validation', async () => {
        const nameInput = getName();
        await user.type(nameInput, 'Me');
        
        const emailInput = getEmail();
        await user.type(emailInput, 'viewer@gmail.id');

        await clickButtonSimpan();
        // 手动触发表单提交(如果按钮是type="button"的话)
        await user.submit(screen.getByRole('form', { name: /account/i }));

        await waitFor(() => {
            expect(onSubmit).toHaveBeenCalledWith({
                fullname: 'Me', // 注意:输入框name是fullname,所以这里要对应
                email: 'viewer@gmail.id',
            });
        });

        expect(onSubmit).toHaveBeenCalledTimes(1);
    });
});

function getName() {
    const input = screen.getByRole('textbox', { name: /name/i });
    user.click(input);
    return input;
}

function getEmail() {
    const input = screen.getByRole('textbox', { name: /email/i });
    user.click(input);
    return input;
}

async function clickButtonSimpan() {
    await user.click(screen.getByRole('button', { name: /save/i }));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:55:22