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

如何用Testing Library测试React Table行过滤?解决onChange未触发问题

React Table 搜索过滤测试问题

我用react-table做了个数据展示组件,现在写测试用例验证搜索框输入后是否能过滤表格行,但测试里没触发onChange事件,搜索功能没执行。下面是组件和测试代码示例,请问怎么测试这个场景?有没有更优的测试方案?

组件代码(data-table.tsx)

import {
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getSortedRowModel,
  useReactTable,
  ColumnDef,
} from '@tanstack/react-table';
import { useMemo, useState } from 'react';
import { DebouncedInput } from './debounced-input';
import { Data, data } from './data';
export function DataTable() {
  const columns: Array<ColumnDef<Data>> = useMemo(
    () => [
      {
        accessorKey: 'id',
      },
      {
        accessorKey: 'name',
        cell({ row }) {
          return (
            <div className="border-0 rounded-md">
              <div className="p-3">
                <p className="break-all">{row.getValue('name')}</p>
              </div>
            </div>
          );
        },
      },
    ],
    []
  );
  const getRowId = (row: Data) => row.id ?? '';
  const [searchTerm, setSearchTerm] = useState('');
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    enableMultiRowSelection: false,
    getRowId,
    state: {
      globalFilter: searchTerm,
    },
  });

  return (
    <>
      <DebouncedInput
        data-testid="debounced-input"
        type="text"
        className="p-1 mr-2 mb-2"
        value={searchTerm}
        onChange={setSearchTerm}
        placeholder="Search"
      />
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th>Name</th>
          </tr>
        </thead>
        <tbody>
          {table.getRowModel().rows?.length ? (
            table.getRowModel().rows.map((row) => (
              <tr className="cursor-pointer" key={row.id}>
                {row.getVisibleCells().map((cell) => (
                  <td key={cell.id}>
                    {flexRender(cell.column.columnDef.cell, cell.getContext())}
                  </td>
                ))}
              </tr>
            ))
          ) : (
            <tr>
              <td colSpan={columns.length} className="h-24 text-center">
                No Data
              </td>
            </tr>
          )}
        </tbody>
      </table>
    </>
  );
}

测试代码(data-table.spec.tsx)

import { render, screen, act } from '@testing-library/react';
import { DataTable } from './data-table';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

describe('Tests', () => {
  it('should have searched license in the grid when user type a term', async () => {
    const user = userEvent.setup();
    render(<DataTable />);
    await act(async () => {
      await user.type(screen.getByTestId('debounced-input'), 'john');
    });

    expect(screen.getByText(/john doe/i)).toBeInTheDocument();
    expect(screen.getByText(/jane doe/i)).not.toBeInTheDocument();
    //
  });
});

问题根源

测试失效核心原因是DebouncedInput是防抖组件——它不会在输入每个字符时立刻触发onChange,而是要等防抖延迟结束后才执行回调。你当前的测试在输入完成后立刻断言,此时防抖逻辑还没触发,自然看不到过滤效果。

修复方案

1. 等待防抖触发(推荐)

用Testing Library的waitFor工具,自动等待直到断言条件满足,不用手动设置定时器,更稳定:

import { render, screen, waitFor } from '@testing-library/react';
import { DataTable } from './data-table';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';

describe('Tests', () => {
  it('should filter table rows when user types a search term', async () => {
    const user = userEvent.setup();
    render(<DataTable />);
    
    await user.type(screen.getByTestId('debounced-input'), 'john');
    
    // 等待防抖逻辑执行,直到断言条件成立
    await waitFor(() => {
      expect(screen.getByText(/john doe/i)).toBeInTheDocument();
      // 用queryByText检查元素不存在,getByText找不到会抛错
      expect(screen.queryByText(/jane doe/i)).not.toBeInTheDocument();
    });
  });
});

2. Mock防抖组件(跳过真实防抖)

如果不想等待真实的防抖时间,可以把DebouncedInput mock成普通input,让输入时立刻触发onChange:

import { render, screen } from '@testing-library/react';
import { DataTable } from './data-table';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { DebouncedInput } from './debounced-input';

// 将防抖组件替换为普通input
jest.mock('./debounced-input', () => ({
  DebouncedInput: (props: any) => <input {...props} />
}));

describe('Tests', () => {
  it('should filter table rows when user types a search term', async () => {
    const user = userEvent.setup();
    render(<DataTable />);
    
    await user.type(screen.getByTestId('debounced-input'), 'john');
    
    expect(screen.getByText(/john doe/i)).toBeInTheDocument();
    expect(screen.queryByText(/jane doe/i)).not.toBeInTheDocument();
  });
});

更优测试方案

1. 拆分测试关注点

把测试拆成两部分,避免耦合:

  • 单独测试DebouncedInput:验证它在防抖延迟后能正确触发onChange;
  • 单独测试表格过滤逻辑:直接控制搜索关键词,验证表格行是否正确过滤,不用依赖输入组件的交互:
// 先把DataTable改成受控组件(可选优化)
export function DataTable({ searchTerm = '' }: { searchTerm?: string }) {
  const [internalSearchTerm, setInternalSearchTerm] = useState(searchTerm);
  // 保留原有逻辑,用internalSearchTerm作为globalFilter
  // ...
}

// 测试过滤逻辑
it('should filter rows based on search term', () => {
  render(<DataTable searchTerm="john" />);
  
  expect(screen.getByText(/john doe/i)).toBeInTheDocument();
  expect(screen.queryByText(/jane doe/i)).not.toBeInTheDocument();
});

2. 聚焦用户行为

测试用户可见的结果,而非组件内部状态。比如用户输入后,检查表格显示的内容是否符合预期,而不是去断言searchTerm的取值。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 02:25:55