Testing
How to write tests against a table that survive a redesign: query by role, drive the engine directly, and control the clock.
Query by role, not by class
The table renders real semantics, which is exactly what a test should hold on to. A class name is an implementation detail; an accessible name is a promise.
await user.click(screen.getByRole('button', { name: 'Sort by Name' }));
expect(screen.getByRole('columnheader', { name: /Name/ })).toHaveAttribute(
'aria-sort',
'ascending',
);
Rows are row, cells are cell or columnheader, and a selectable table is a grid rather than
a table. That is the ARIA rule, not a quirk, and asserting it is a useful regression test in
itself.
Control the clock
Search and filters are debounced, so a test that types and asserts immediately will fail for the right reason. Either use fake timers, or remove the delay:
render(<DataTable searchDebounceMs={0} searchMinLength={0} data={rows} columns={columns} />);
Setting both to zero in tests is legitimate: you are testing the filtering, not the debounce. Test the debounce once, on its own, with fake timers.
Test the engine without the DOM
Most table logic is not about rendering. createTable gives you the engine as a plain object,
with no React and no jsdom:
const table = createTable({ data, columns, getRowId: (r) => r.id });
table.setSorting([{ id: 'age', desc: true }]);
expect(table.getRowModel().rows.map((r) => r.getValue('age'))).toEqual([45, 36, 28]);
These tests are fast and they do not break when the markup changes. Use them for comparators, filters, aggregates and query building; save rendered tests for interaction.
Fake the server, not the fetch
createLocalDataSource wraps an array in the same interface a REST source has, so a server-mode
table can be tested without a network. It takes the table's columns, so it filters and sorts the
way the table would:
<DataTable
dataMode="server"
dataSource={createLocalDataSource(rows, { columns })}
columns={columns}
/>
The table takes the same path it would in production (query, fetch, loading, empty), which is what makes the test worth having.
Wait for what you mean
Assert on the result, not on a timeout. Testing Library's findBy* queries and waitFor retry
until the table has settled; a fixed sleep is either flaky or slow, and usually both.
Accessibility in the suite
An axe assertion per configuration catches the regressions that reviews miss: a slot that dropped its props, a custom cell that nested a button inside a button. Run it against the configurations you actually ship, not just the defaults.
Visual tests, carefully
Screenshots catch what assertions cannot and fail for reasons that have nothing to do with your change. Store baselines per platform, seed the data so every run renders the same rows, and keep them in their own suite so a font update does not block a bug fix.
Related
- Headless: the engine these tests drive.
- Accessibility: what the roles mean.
- Client and server: data sources.
- Best practices