React Testing Library
"The more your tests resemble the way your software is used, the more confidence they can give you."
RTL docs
Some things are different π₯π₯π₯π₯π₯
What do users interact with?
DOM
What should our tests interact with?
DOM
Render or do not...
...there is no "shallow."
import { render } from '@testing-library/react';
render(
<I18nProvider>
<GroupPreview {...defaultProps} />
</I18nProvider>
);
But you can still mock π
jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
useResizeObserver: jest.fn(),
}));
But... most of the time you shouldn't π
"The more your tests resemble the way your software is used, the more confidence they can give you."
RTL docs
No more access to sub props.
In a past life...
component.find(DatePickerComponent).prop(βonDateChangeβ)(newDate);
The bright future π
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
The bright future π
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
The bright future π
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
The bright future π
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
Asynchronous stuff
Asynchronous stuff: Enzyme
await wrapper
.find(`.kbnFieldButton__button`)
.simulate('click');
expect(something).toHaveBeenCalledWith(something expected);
FAIL
π€
await act(async () => {
await wrapper
.find(`.kbnFieldButton__button`)
.simulate('click');
});
expect(something).toHaveBeenCalledWith(something expected);
FAIL
π§
await act(async () => {
await wrapper
.find(`.kbnFieldButton__button`)
.simulate('click');
});
wrapper.update();
expect(something).toHaveBeenCalledWith(something expected);
FAIL
π
await act(async () => {
await wrapper
.find(`.kbnFieldButton__button`)
.simulate('click');
});
wrapper.update();
await new Promise(resolve => setTimeout(resolve, 0));
expect(something).toHaveBeenCalledWith(something expected);
FAIL
π‘
jest.mock('lodash', () => {
const original = jest.requireActual('lodash');
return {
...original,
debounce: (fn: unknown) => fn,
};
});
...
await act(async () => {
await wrapper
.find(`.kbnFieldButton__button`)
.simulate('click');
});
wrapper.update();
await new Promise(resolve => setTimeout(resolve, 0));
expect(something).toHaveBeenCalledWith(something expected);
PASS
π
π€¨ Does this test actually mean anything?
Asynchronous stuff: RTL
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
expect(something).toHaveBeenCalledWith(something expected);
FAIL
π
import { ... waitFor } from '@testing-library/react';
...
userEvent.click(screen.getByRole('link', { name: 'Last five minutes' }));
await waitFor(() => {
expect(something).toHaveBeenCalledWith(something expected);
});
PASS
π
Accessing DOM elements
3 core operations
- get β throws if not found
- query β returns empty if not found
- find β like get + waitFor
Singular (strict)
- getBy...
- queryBy...
- findBy...
Plural
- getAllBy...
- queryAllBy...
- findAllBy...
Best!
- getByRole
- getByLabelText
- getByPlaceholderText
- getByText
- getByDisplayValue
Interacting with the DOM
import { ..., screen, fireEvent } from '@testing-library/react';
fireEvent.click(await screen.findByText(label));
elem.dispatchEvent(event);
"The more your tests resemble the way your software is used, the more confidence they can give you."
RTL docs
RTL π€ user-event
import { ..., screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
userEvent.click(await screen.findByText(label));
RTL π€ jest-dom
expect(screen.getByTestId('button')).toBeDisabled();
expect(screen.queryByRole('combo-box')).not.toBeInTheDocument();
Up next in testing
- Update RTL dependencies β¬οΈ
- Adoption tracking/team pulse
- Team testing strategy document
- Other ideas from the team π‘