Cancelling Asynchronous Tasks During a Jest Run

Jul 15, 2020

I was running some tests on a react-router-dom Component Class, using React Testing Library and Jest.

The class makes a fetch call to my API and returns the list of posts for this blog. The list then has links to edit or deactivate the posts..

Running the page by itself, I receive no errors.

Warning

But when I run it through Jest, I receive a warning, even though all the tests passed.

console.error node_modules/@testing-library/react/dist/act-compat.js:52
Warning: Can't perform a React state update on an unmounted component. 
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method. in Posts (at App.js:57) in Route (at App.js:56) in Switch (at App.js:53) in Router (created by BrowserRouter) in BrowserRouter (at App.js:43) in App (at App.test.js:83) in Router (at App.test.js:65)

Test Scenario

My test is simulating a click on a Posts link.

it('should navigate to the posts page resolved', async () => {
	const { getByText , getByTestId } = renderWithRouter()

	fireEvent.click(getByTestId('Posts'))

	const posts = await waitForElement(() => getByText("Title"))

	expect(posts).toHaveTextContent('Title')
})

Solution

I googled "cancel all subscriptions and asynchronous tasks in the componentwillunmount method" and came across this solution.

Based on the solution, I added an _isMounted variables to the class's this scope, to keep track of the fetch callback and to set when mounting and unmounting the component: 

Constructor

this._isMounted = false;

Fetch

In my fetch result and error blocks:

getPosts() {
fetch(`${process.env.REACT_APP_API_URL}/getPosts`)
	.then(res => res.json())
	.then(
		result => {
			this._isMounted && this.setState({
				isLoaded	: true,
				entries		: result.posts,
				activeEntries	: result.posts.filter(entry => entry.deletedAt === null)
			})
		},
		error => {
			this._isMounted && this.setState({
				isLoaded	: false,
				error
			})
		}
	)
}

componentDidMount() / componentWillUnmount()

componentDidMount() {
	this._isMounted = true;
	this._isMounted && this.getPosts();
}

componentWillUnmount() {
	this._isMounted = false;
 }

Code commit here.

Comments

New Comment