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

React Admin测试覆盖率缺失:如何测试window.scrollTo与onSuccess?

问题描述

执行npm run test:coverage后得到未覆盖代码报告:

文件语句覆盖率%分支覆盖率%函数覆盖率%行覆盖率%未覆盖行号
TestCreate.tsx55.551004055.5519-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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 13:01:02