Testing
This page describes how to test an application built with React Aria. It documents the available testing utilities available for each aria pattern and how they can be used to simulate common user interactions.
Introduction#
Running automated tests on your application helps ensure that it continues to work as expected over time. You can use testing tools like React Testing Library along with test runners like Jest or Mocha to test applications built with React Aria Components or hooks. These generally work quite well out of the box but there are a few things to consider to ensure your tests are the best they can be.
The information below covers best practices when writing tests, and be sure to checkout our test utils that incorporate these strategies under the hood, helping streamline the test writing practice for you.
Testing semantics#
Many testing libraries expect you to query for elements in the DOM tree. For example, you might have a test that renders a login page, finds the username and password fields, and simulates filling them out and submitting the form.
The recommended way to query for React Aria Components and their internals is by semantics. React Aria Components implement ARIA patterns. ARIA is a W3C standard that specifies the semantics for many UI components. This is used to expose them to assistive technology such as screen readers, but can also be used in tests to operate the application programmatically. These semantics are much less likely to change over time, and while other DOM nodes may be added or removed, the semantics are more likely to stay stable.
The main attribute to look for when querying is the role. This attribute represents the type of element a DOM node represents, e.g. a button, list option, or tab.
React Testing Library#
React Testing Library is useful because it enforces that you write tests using semantics instead of implementation details. We use React Testing Library to test React Aria itself, and it's quite easy to query elements by role, text, label, etc.
import {render} from '@testing-library/react';
let tree = render(<MyComponent />);
let option = tree.getByRole('button');
import {render} from '@testing-library/react';
let tree = render(<MyComponent />);
let option = tree.getByRole('button');
import {render} from '@testing-library/react';
let tree = render(
<MyComponent />
);
let option = tree
.getByRole('button');
Test ids#
Querying by semantics covers many scenarios, but what if you have many buttons on a page? How do you find the specific button you're looking for in a test? In many cases this could be done by querying by the text in the button or an accessibility label associated with it, but sometimes this might change over time or may be affected by things like translations in different languages. In these cases, you may need a way to identify specific elements in tests, and that's where test ids come in.
React Aria Components pass all data attributes
through to their underlying DOM nodes, which allows you to use an attribute like data-testid
to identify
a particular instance of a component. For example, you could add test ids to the two input elements
in a login form and use them to find the username and password fields.
This example uses React Testing Library, but the idea could be applied in a similar way with other testing libraries.
import {render} from '@testing-library/react';
import {Input, Label, TextField} from 'react-aria-components';
function LoginForm() {
return (
<>
<TextField data-testid="username">
<Label>Username</Label>
<Input />
</TextField>
<TextField data-testid="password">
<Label>Username</Label>
<Input />
</TextField>
</>
);
}
let tree = render(<LoginForm />);
let username = tree.getByTestId('username');
let password = tree.getByTestId('password');
import {render} from '@testing-library/react';
import {
Input,
Label,
TextField
} from 'react-aria-components';
function LoginForm() {
return (
<>
<TextField data-testid="username">
<Label>Username</Label>
<Input />
</TextField>
<TextField data-testid="password">
<Label>Username</Label>
<Input />
</TextField>
</>
);
}
let tree = render(<LoginForm />);
let username = tree.getByTestId('username');
let password = tree.getByTestId('password');
import {render} from '@testing-library/react';
import {
Input,
Label,
TextField
} from 'react-aria-components';
function LoginForm() {
return (
<>
<TextField data-testid="username">
<Label>
Username
</Label>
<Input />
</TextField>
<TextField data-testid="password">
<Label>
Username
</Label>
<Input />
</TextField>
</>
);
}
let tree = render(
<LoginForm />
);
let username = tree
.getByTestId(
'username'
);
let password = tree
.getByTestId(
'password'
);
Triggering events#
Most testing libraries include a way to simulate events on an element. React Aria Components rely on many different browser events to support different devices and platforms, so it's important to simulate these correctly in your tests. For example, rather than only simulating a click event, the tests should simulate all of the events that would occur if a real user were interacting with the component.
For example, a click is really a mousemove
and mouseover
the target, followed
by mousedown
, focus
, and mouseup
events, and finally a click
event. If you only simulated the click
event, you would be missing all of these other preceding events that occur in real-world situations and this
may make your test not work correctly. The implementation of the component may also change in the future to
expect these events, making your test brittle. In addition, browsers have default behavior that occurs on
certain events which would be missing, like focusing elements on mouse down, and toggling checkboxes on click.
The best way to handle this is with the user-event library. This lets you trigger high level interactions like a user would, and the library handles firing all of the individual events that make up that interaction. If you use this library rather than firing events manually, your tests will simulate real-world behavior much better and work as expected.
user-event can handle many types of interactions, e.g. clicks, tabbing, typing, etc. This example shows how you could use it to render a login form and enter text in each field and click the submit button, just as a real user would.
import {render} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
let tree = render(<LoginForm />);
// Click on the username field to focus it, and enter the value.
userEvent.click(tree.getByLabelText('Username'));
userEvent.type(document.activeElement, 'devon');
// Tab to the password field, and enter the value.
userEvent.tab();
userEvent.type(document.activeElement, 'Pas$w0rd');
// Tab to the submit button and click it.
userEvent.tab();
userEvent.click(document.activeElement);
import {render} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
let tree = render(<LoginForm />);
// Click on the username field to focus it, and enter the value.
userEvent.click(tree.getByLabelText('Username'));
userEvent.type(document.activeElement, 'devon');
// Tab to the password field, and enter the value.
userEvent.tab();
userEvent.type(document.activeElement, 'Pas$w0rd');
// Tab to the submit button and click it.
userEvent.tab();
userEvent.click(document.activeElement);
import {render} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
let tree = render(
<LoginForm />
);
// Click on the username field to focus it, and enter the value.
userEvent.click(
tree.getByLabelText(
'Username'
)
);
userEvent.type(
document.activeElement,
'devon'
);
// Tab to the password field, and enter the value.
userEvent.tab();
userEvent.type(
document.activeElement,
'Pas$w0rd'
);
// Tab to the submit button and click it.
userEvent.tab();
userEvent.click(
document.activeElement
);
React Aria test utils alpha#
@react-aria/test-utils is a set of testing utilities that aims to make writing unit tests easier for consumers of React Aria or for users who have built their own components following the respective ARIA pattern specification. By using the ARIA specification for any given component pattern as a source of truth, we can make assumptions about the existence of various aria attributes in a component. This allows us to navigate the component's DOM structure, simulate common interactions, and verify the the resulting state of the component.
Installation#
@react-aria/test-utils
can be installed using a package manager like npm or yarn.
yarn add --dev @react-aria/test-utils
Please note that this library uses @testing-library/react@15 and @testing-library/user-event. This means that you need to be on React 18+ in order for these utilities to work.
Setup#
Once installed, you can access the User
that @react-aria/test-utils
provides in your test file as shown below. This user only needs to be initialized once and then can be used to generate
specific ARIA pattern tester in your test cases. This gives you access to that pattern's specific utilities that you can then call within your test to query for specific subcomponents or simulate common interactions.
See below for what patterns are currently supported.
// YourTest.test.ts
import {screen} from '@testing-library/react';
import {User} from '@react-aria/test-utils';
// Provide whatever method of advancing timers you use in your test, this example assumes Jest with fake timers.
// 'interactionType' specifies what mode of interaction should be simulated by the tester
// 'advanceTimer' is used by the tester to advance the timers in the tests for specific interactions (e.g. long press)
let testUtilUser = new User({
interactionType: 'mouse',
advanceTimer: jest.advanceTimersByTime
});
// ...
it('my test case', async function () {
// Render your test component/app
render();
// Initialize the table tester via providing the 'Table' pattern name and the root element of said table
let table = testUtilUser.createTester('Table', {
root: screen.getByTestId('test_table')
});
// ...
});
// YourTest.test.ts
import {screen} from '@testing-library/react';
import {User} from '@react-aria/test-utils';
// Provide whatever method of advancing timers you use in your test, this example assumes Jest with fake timers.
// 'interactionType' specifies what mode of interaction should be simulated by the tester
// 'advanceTimer' is used by the tester to advance the timers in the tests for specific interactions (e.g. long press)
let testUtilUser = new User({
interactionType: 'mouse',
advanceTimer: jest.advanceTimersByTime
});
// ...
it('my test case', async function () {
// Render your test component/app
render();
// Initialize the table tester via providing the 'Table' pattern name and the root element of said table
let table = testUtilUser.createTester('Table', {
root: screen.getByTestId('test_table')
});
// ...
});
// YourTest.test.ts
import {screen} from '@testing-library/react';
import {User} from '@react-aria/test-utils';
// Provide whatever method of advancing timers you use in your test, this example assumes Jest with fake timers.
// 'interactionType' specifies what mode of interaction should be simulated by the tester
// 'advanceTimer' is used by the tester to advance the timers in the tests for specific interactions (e.g. long press)
let testUtilUser =
new User({
interactionType:
'mouse',
advanceTimer:
jest
.advanceTimersByTime
});
// ...
it('my test case', async function () {
// Render your test component/app
render();
// Initialize the table tester via providing the 'Table' pattern name and the root element of said table
let table =
testUtilUser
.createTester(
'Table',
{
root: screen
.getByTestId(
'test_table'
)
}
);
// ...
});
See below for the full definition of the User
object.
Properties
Name | Type | Default | Description |
interactionType | UserOpts['interactionType'] | mouse | The interaction type (mouse, touch, keyboard) that the test util user will use when interacting with a component. This can be overridden at the aria pattern util level if needed. |
advanceTimer | UserOpts['advanceTimer'] | — | A function used by the test utils to advance timers during interactions. Required for certain aria patterns (e.g. table). |
Methods
Method | Description |
constructor(
(opts: UserOpts
)): void | |
createTester<T extends PatternNames>(
(patternName: T,
, opts: TesterOpts<T>
)): Tester<T> | Creates an aria pattern tester, inheriting the options provided to the original user. |
Patterns#
Below is a list of the ARIA patterns testers currently supported by createTester
. See the accompanying component testing docs pages for a sample of how to use
the testers in your test suite.