snippetjavascriptTip
Testing React components that update asynchronously with React Testing Library
Viewed 0 times
testingcomponentsupdatethatwithreactasynchronouslylibrary
Problem
Recently, while working on a side-project, we started using the React DnD library, as we wanted to implement a multi-container drag and drop system with cards.
After spending the better part of a day implementing the functionality, we decided to add some tests to ensure everything will keep working as expected. In the aforementioned project, we use React Testing Library to write tests for our components.
While testing the drag functionality, we came across a very stubborn test. Here's a simplified version of our
And here's the test we were trying to write originally:
While the test was obviously not working, the console was constantly nagging about wrapping the test in
After spending the better part of a day implementing the functionality, we decided to add some tests to ensure everything will keep working as expected. In the aforementioned project, we use React Testing Library to write tests for our components.
While testing the drag functionality, we came across a very stubborn test. Here's a simplified version of our
Card component:And here's the test we were trying to write originally:
While the test was obviously not working, the console was constantly nagging about wrapping the test in
act():Solution
import React from 'react';
import { useDrag } from 'react-dnd';
const Card = ({
card: {
id,
title
}
}) => {
const [style, drag] = useDrag({
item: { id, type: 'card' },
collect: monitor => ({
opacity: monitor.isDragging() ? 0 : 1
})
});
return (
<li className="card" id={id} ref={drag} style={style}>
{title}
</li>
);
};While testing the drag functionality, we came across a very stubborn test. Here's a simplified version of our
Card component:And here's the test we were trying to write originally:
While the test was obviously not working, the console was constantly nagging about wrapping the test in
act():This message wasn't very helpful in identifying the underlying issue. The only thing it highlighted was that the test didn't update the component style immediately. There were pending updates after the test completed. To put it plainly, the test was failing because the
dragStart event didn't immediately update the Card components' style (i.e. set the new opacity).As a side note, the
Card component is connected to Redux, which might relate to the issue, but it would most likely happen even without Redux. That's probably due to the fact that collect takes some amount of time to run and send an update to the component.Digging deeper, we found that apart from
act(), there are also other options, such as waitFor() and waitForDomChange(). These seem more intuitive simply because of the name and way they're written (using either async await or promises). However, waitForDomChange() didn't work properly for our case and our version of react-testing-library (which shipped with react-scripts) was outdated and did not export waitFor(), which took us a good half an hour to figure out.Code Snippets
import React from 'react';
import { useDrag } from 'react-dnd';
const Card = ({
card: {
id,
title
}
}) => {
const [style, drag] = useDrag({
item: { id, type: 'card' },
collect: monitor => ({
opacity: monitor.isDragging() ? 0 : 1
})
});
return (
<li className="card" id={id} ref={drag} style={style}>
{title}
</li>
);
};import React from 'react';
import { fireEvent } from '@testing-library/react';
import Card from './components/Card';
// This a little helper we have written to connect to redux and react-dnd
import renderDndConnected from './test_utils/renderDndConnected';
describe('<Card/>', () => {
let card;
beforeEach(() => {
const utils = renderDndConnected(
<Card card={{ id: '1', title: 'Card' }} />
);
card = utils.container.querySelector('.card');
});
it('initial opacity is 1', () => {
expect(card.style.opacity).toEqual('1');
});
describe('when drag starts', () => {
beforeEach(() => {
fireEvent.dragStart(card);
});
it('opacity is 0', () => {
expect(card.style.opacity).toEqual('0');
});
});
});When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser.Context
From 30-seconds-of-code: testing-async-components
Revisions (0)
No revisions yet.