act๋ ํ
์คํธ ํฌํผHelper๋ก, ๋๊ธฐ ์ค์ธ React ์
๋ฐ์ดํธ๋ฅผ ๋ชจ๋ ์ ์ฉํ ๋ค ๋จ์ธAssertํ ์ ์๊ฒ ๋์์ ์ค๋๋ค.
await act(async actFn)์ปดํฌ๋ํธ๋ฅผ ๋จ์ธAssertionํ ์ ์๋๋ก ์ค๋นํ๋ ค๋ฉด await act() ํธ์ถ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ณ ์
๋ฐ์ดํธํ๋ ์ฝ๋๋ฅผ ๊ฐ์ธ์ธ์. ์ด๋ ๊ฒ ํ๋ฉด ํ
์คํธ๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์๋ํ๋ ์ค์ React ๋ฐฉ์๊ณผ ๋ ์ ์ฌํ๊ฒ ์คํ๋ฉ๋๋ค.
๋ ํผ๋ฐ์ค
await act(async actFn)
UI ํ
์คํธ๋ฅผ ์์ฑํ ๋ ๋ ๋๋ง, ์ฌ์ฉ์ ์ด๋ฒคํธ, ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ๋ฑ์ ์ฌ์ฉ์ ์ธํฐํ์ด์ค์์ ์ํธ์์ฉ โ๋จ์โ๋ก ๋ณผ ์ ์์ต๋๋ค. React๋ act()๋ผ๋ ํฌํผ๋ฅผ ์ ๊ณตํ๋๋ฐ ์ด๋ ์ด โ๋จ์โ์ ๊ด๋ จ๋ ๋ชจ๋ ์
๋ฐ์ดํธ๊ฐ DOM์ ์ ์ฉ๋๊ธฐ ์ ๊น์ง ๋จ์ธ์ด ์คํ๋์ง ์๋๋ก ๋ณด์ฅํด ์ค๋๋ค.
act ๋ผ๋ ์ด๋ฆ์ Arrange-Act-Assert ํจํด์์ ๋ฐ์จ ๊ฒ์
๋๋ค.
it ('renders with button disabled', async () => {
await act(async () => {
root.render(<TestComponent />)
});
expect(container.querySelector('button')).toBeDisabled();
});๋งค๊ฐ๋ณ์
async actFn: ํ ์คํธํ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๊ฑฐ๋ ์ํธ์์ฉ์ ์ํํ๋ ๋น๋๊ธฐ ํจ์์ ๋๋ค.actFn๋ด๋ถ์์ ๋ฐ์ํ๋ ์ ๋ฐ์ดํธ๋ ๋ด๋ถ act ํ์ ์ถ๊ฐ๋๋ฉฐ ๋ชจ๋ ๋ชจ์์ DOM์ ์ ์ฉ๋ฉ๋๋ค. ๋น๋๊ธฐ ํจ์์ด๊ธฐ ๋๋ฌธ์ React๋ ๋น๋๊ธฐ ๊ฒฝ๊ณ๋ฅผ ๋๋ ์ฝ๋๋ ์คํํ๊ณ ์์ฝ๋ ์ ๋ฐ์ดํธ๋ ํจ๊ป ์ฒ๋ฆฌํฉ๋๋ค.
๋ฐํ๊ฐ
act๋ ์๋ฌด ๊ฐ๋ ๋ฐํํ์ง ์์ต๋๋ค.
์ฌ์ฉ๋ฒ
์ปดํฌ๋ํธ๋ฅผ ํ
์คํธํ ๋ act๋ฅผ ์ฌ์ฉํ๋ฉด ์ถ๋ ฅ ๊ฒฐ๊ณผ์ ๋ํ ๋จ์ธ์ ๋ ์์ ํ๊ฒ ํ ์ ์์ต๋๋ค.
์์๋ก Counter๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ณ ์๋ ์ฌ์ฉ ์์๋ ์ด๋ฅผ ํ
์คํธํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => prev + 1);
}
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>
Click me
</button>
</div>
)
}ํ ์คํธ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ ๋ฐฉ๋ฒ
์ปดํฌ๋ํธ์ ๋ ๋๋ง ๊ฒฐ๊ณผ๋ฅผ ํ
์คํธํ๋ ค๋ฉด ๋ ๋๋ง ์ฝ๋๋ฅผ act()๋ก ๊ฐ์ธ์ผ ํฉ๋๋ค.
import {act} from 'react';
import ReactDOMClient from 'react-dom/client';
import Counter from './Counter';
it('can render and update a counter', async () => {
container = document.createElement('div');
document.body.appendChild(container);
// โ
์ปดํฌ๋ํธ๋ฅผ act() ์์์ ๋ ๋๋งํฉ๋๋ค.
await act(() => {
ReactDOMClient.createRoot(container).render(<Counter />);
});
const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 0 times');
expect(document.title).toBe('You clicked 0 times');
});์ ์์์์๋ ์ปจํ
์ด๋๋ฅผ ๋ง๋ค๊ณ ๋ฌธ์์ ์ถ๊ฐํ ๋ค Counter ์ปดํฌ๋ํธ๋ฅผ act() ์์์ ๋ ๋๋งํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ๊ฐ ๋ ๋๋ง๋๊ณ ํจ๊ณผ๊ฐ ์ ์ฉ๋ ํ์ ๋จ์ธ์ ์ํํ ์ ์์ต๋๋ค.
act๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ์
๋ฐ์ดํธ๊ฐ ์ ์ฉ๋ ๋ค ๋จ์ธ์ ์คํํ ์ ์์ต๋๋ค.
ํ ์คํธ์์ ์ด๋ฒคํธ ๋์คํจ์นญํ๋ ๋ฐฉ๋ฒ
์ด๋ฒคํธ๋ฅผ ํ
์คํธํ๋ ค๋ฉด ์ด๋ฒคํธ๋ฅผ act()๋ก ๊ฐ์ธ์ธ์.
import {act} from 'react';
import ReactDOMClient from 'react-dom/client';
import Counter from './Counter';
it.only('can render and update a counter', async () => {
const container = document.createElement('div');
document.body.appendChild(container);
await act( async () => {
ReactDOMClient.createRoot(container).render(<Counter />);
});
// โ
์ด๋ฒคํธ ๋์คํจ์น๋ฅผ act() ์์์ ์คํํฉ๋๋ค.
await act(async () => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
const button = container.querySelector('button');
const label = container.querySelector('p');
expect(label.textContent).toBe('You clicked 1 times');
expect(document.title).toBe('You clicked 1 times');
});์ ์์์์๋ ์ปดํฌ๋ํธ๋ฅผ ๋จผ์ act๋ก ๊ฐ์ธ ๋ ๋๋งํ๊ณ , ์ด๋ฒคํธ ๋์คํจ์น๋ act()๋ก ๊ฐ์๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํด๋น ์ด๋ฒคํธ๋ก ์ธํ ๋ชจ๋ ์
๋ฐ์ดํธ๊ฐ ์ ์ฉ๋ ๋ค ๋จ์ธ์ด ์ํ๋ฉ๋๋ค.
๋ฌธ์ ํด๊ฒฐ
โThe current testing environment is not configured to support act(โฆ)โ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ
act๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ํ
์คํธ ํ๊ฒฝ์์ global.IS_REACT_ACT_ENVIRONMENT=true๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค. ์ด ์ค์ ์ act๊ฐ ์ฌ๋ฐ๋ฅธ ํ๊ฒฝ์์๋ง ์ฌ์ฉ๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ด ์ ์ญ ์ค์ ์ด ์์ผ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ค๋ฅ๊ฐ ํ์๋ฉ๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด React ํ ์คํธ๋ฅผ ์ํ ์ ์ญ ์ค์ ํ์ผ์ ๋ค์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ธ์.
global.IS_REACT_ACT_ENVIRONMENT=true