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

