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

