React Admin测试覆盖率缺失:如何测试window.scrollTo与onSuccess?
问题描述
执行npm run test:coverage后得到未覆盖代码报告:
| 文件 | 语句覆盖率% | 分支覆盖率% | 函数覆盖率% | 行覆盖率% | 未覆盖行号 |
|---|---|---|---|---|---|
| TestCreate.tsx | 55.55 | 100 | 40 | 55.55 | 19-20, 32-38 |
未覆盖部分对应SimpleToolbar组件中的window.scrollTo方法以及mutation options的onSuccess回调,请问如何对这两部分进行测试?
相关源码
TestCreate.tsx
import * as React from 'react'; import { Create, SimpleForm, SaveButton, useNotify, TextInput, CreateProps, Toolbar, required, } from 'react-admin'; import { Button } from '@mui/material'; import { useFormContext } from 'react-hook-form'; function SimpleToolbar() { const notify = useNotify(); const { reset } = useFormContext(); const onSuccess = (data: { email: string }): void => { window.scrollTo(0, 0); notify(`${data.email} submit successfully.`, { type: 'success', messageArgs: 'form has been submitted', }); }; return ( <Toolbar> <SaveButton type="button" label="Submit" mutationOptions={{ onSuccess: (data) => { onSuccess(data); }, }} /> <Button type="reset" onClick={() => { reset(); }} > CLEAR </Button> </Toolbar> ); } function TestCreate(props: CreateProps) { return ( <Create {...props}> <SimpleForm toolbar={<SimpleToolbar />}> <TextInput source="name" label="Name" validate={required()} /> <TextInput source="email" label="Email" validate={required()} /> </SimpleForm> </Create> ); } export default TestCreate;
TestCreate.spec.tsx
import { render, screen } from '@testing-library/react'; import { AdminContext, ResourceContextProvider, testDataProvider } from 'react-admin'; import TestCreate from './TestCreate'; describe('<TestCreate />', () => { const dataProvider = testDataProvider({ create: jest.fn().mockResolvedValueOnce({ data: { name: 'Jason', email: 'jason@gmail.com' } }), }); it('should render components', async () => { render( <AdminContext dataProvider={dataProvider as any}> <ResourceContextProvider value="test"> <TestCreate /> </ResourceContextProvider> </AdminContext>, ); const nameInput = await screen.findByText('Name'); expect(nameInput).toBeInTheDocument(); const emailInput = await screen.findByText('Email'); expect(emailInput).toBeInTheDocument(); }); });
解决方案
要覆盖这两部分未测试的代码,你需要模拟window.scrollTo方法,并测试表单提交成功后的逻辑,包括onSuccess回调的执行、通知的触发以及滚动行为。以下是具体步骤:
1. 模拟依赖并测试提交流程
在测试文件中,通过mock工具替换window.scrollTo和useNotify钩子,然后模拟表单提交流程验证逻辑:
import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { AdminContext, ResourceContextProvider, testDataProvider, useNotify } from 'react-admin'; import TestCreate from './TestCreate'; // 模拟react-admin的useNotify钩子,方便捕获调用 jest.mock('react-admin', () => ({ ...jest.requireActual('react-admin'), useNotify: jest.fn(), })); describe('<TestCreate />', () => { const mockNotify = useNotify as jest.Mock; const mockScrollTo = jest.fn(); beforeEach(() => { // 每次测试前重置mock状态 mockNotify.mockClear(); mockScrollTo.mockClear(); // 替换window的scrollTo方法为mock函数 window.scrollTo = mockScrollTo; }); const dataProvider = testDataProvider({ create: jest.fn().mockResolvedValue({ data: { name: 'Jason', email: 'jason@gmail.com' } }), }); it('should render components', async () => { render( <AdminContext dataProvider={dataProvider as any}> <ResourceContextProvider value="test"> <TestCreate /> </ResourceContextProvider> </AdminContext>, ); const nameInput = await screen.findByLabelText('Name'); expect(nameInput).toBeInTheDocument(); const emailInput = await screen.findByLabelText('Email'); expect(emailInput).toBeInTheDocument(); }); it('should trigger onSuccess logic when form is submitted', async () => { render( <AdminContext dataProvider={dataProvider as any}> <ResourceContextProvider value="test"> <TestCreate /> </ResourceContextProvider> </AdminContext>, ); // 填充表单输入框 const nameInput = await screen.findByLabelText('Name'); fireEvent.change(nameInput, { target: { value: 'Jason' } }); const emailInput = await screen.findByLabelText('Email'); fireEvent.change(emailInput, { target: { value: 'jason@gmail.com' } }); // 点击提交按钮 const submitButton = await screen.findByText('Submit'); fireEvent.click(submitButton); // 等待异步mutation完成,验证回调逻辑 await waitFor(() => { expect(mockScrollTo).toHaveBeenCalledWith(0, 0); expect(mockNotify).toHaveBeenCalledWith( 'jason@gmail.com submit successfully.', { type: 'success', messageArgs: 'form has been submitted' } ); }); }); });
2. 补充CLEAR按钮的测试(可选)
如果需要覆盖reset函数的调用逻辑,可以添加以下测试用例:
it('should reset form when CLEAR button is clicked', async () => { render( <AdminContext dataProvider={dataProvider as any}> <ResourceContextProvider value="test"> <TestCreate /> </ResourceContextProvider> </AdminContext>, ); // 填充表单 const nameInput = await screen.findByLabelText('Name'); fireEvent.change(nameInput, { target: { value: 'Test Name' } }); expect(nameInput).toHaveValue('Test Name'); // 点击CLEAR按钮 const clearButton = await screen.findByText('CLEAR'); fireEvent.click(clearButton); // 验证输入框被清空 expect(nameInput).toHaveValue(''); });
关键说明
- 通过
jest.mock模拟useNotify,可以直接捕获它的调用参数,验证通知逻辑是否符合预期。 - 替换
window.scrollTo为mock函数,无需实际触发滚动,只需验证函数是否被调用及参数是否正确。 - 使用
waitFor等待异步mutation操作完成,确保所有回调逻辑执行完毕后再进行断言。
内容的提问来源于stack exchange,提问作者DevNooby
相关产品推荐
相关产品推荐

