I'm trying to write a test for a react app. As far as I understand, the act() is supposed to make sure all updates within are finished, including the useEffect.
The problem is, even though the rendering is inside 'act()', the useEffect doesn't finish before the code exists act() if there are multiple tests (each test works on its own but fails when coupled with another test).
I've simplified the code:
The component:
import React, { useEffect, useState } from 'react'
import { Col, Form, Row } from 'react-bootstrap'
import { getProduct } from './getProduct';
export const TestComp = ({productId}) => {
const [cost, setCost] = useState(0);
useEffect(() => {
console.log('called useEffect ' + productId);
getProduct(productId).then(resp => {
const total = resp.data.price * 1;
setCost(total);
}, (error) => {
console.log(error);
alert(error);
});
}, [])
return (
<Form.Group as={Row} controlId={'product' + productId}>
<Col xs={6} md={2} className='cart-item-cost'>
{cost}
</Col>
</Form.Group>
)
}
The test:
import { getNodeText, render, waitFor } from "@testing-library/react"
import { act } from "react-dom/test-utils";
import { TestComp } from "./TestComp";
import { getProduct } from './getProduct';
jest.mock('./getProduct', () => ({
__esModule: true,
getProduct: jest.fn()
}))
test('test 1', () => {
const product = { data: { price: 12.34 }};
const numberOfProducts = 2;
const expectedCost = product.data.price * numberOfProducts;
getProduct.mockImplementation(() => Promise.resolve(product))
act(() => {
render(<TestComp productId={1} />)
})
console.log('test 1 rendered')
const costElement = document.querySelectorAll('.cart-item-cost');
waitFor(() => {
const actualCost = Number(getNodeText(costElement));
expect(actualCost).toBe(expectedCost);
});
})
test('test 2', () => {
const product = { data: { price: 12.34 }};
getProduct.mockImplementation(() => Promise.resolve(product))
act(() =>{
render(<TestComp productId={2} />)
})
console.log('test 2 rendered')
})
The getProduct method is mocked so it doesn't matter here.
The output:
/TestComp.test.js
● Console
console.log
called useEffect 1
at src/components/testowanie/TestComp.js:9:13
console.log
test 1 rendered
at Object.<anonymous> (src/components/testowanie/TestComp.test.js:20:13)
console.log
test 2 rendered
at Object.<anonymous> (src/components/testowanie/TestComp.test.js:36:13)
console.log
called useEffect 2
at src/components/testowanie/TestComp.js:9:13
console.error
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 a useEffect cleanup function.
at TestComp (C:\Projekty\react\LearnReact\src\components\testowanie\TestComp.js:5:27)
10 | getProduct(productId).then(resp => {
11 | const total = resp.data.price * 1;
> 12 | setCost(total);
| ^
13 | }, (error) => {
14 | console.log(error);
15 | alert(error);
The error makes sense. Note the order in the output:
the 1st test logs 'called useEffect 1' and then 'test 1 rendered' - that is what I expect
the 2nd test has the opposite order: 'test 2 rendered' and then 'called useEffect 2' meaning that it called the code inside the act() after it called the code that comes right after it, which stands in the opposition to what the act is supposed to do (that is, make sure all UI changes are executed).
I also tried to await act (and use async test) like this:
test('test', async () => {
//arrange goes here
await act( async () =>{
render(<TestComp productId={2} numberOfProducts={1}/>)
})
console.log('test whatever rendered')
})
Same result.
To me, it seems the act() doesn't work
I'm using: "react": "^17.0.2", "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.4",