React ํ ์คํธ์์ `act` ์ ํธ๋ฆฌํฐ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ์ฌ ์ปดํฌ๋ํธ๊ฐ ์์๋๋ก ๋์ํ๊ฒ ํ๊ณ , ๋น๋๊ธฐ ์ํ ์ ๋ฐ์ดํธ์ ๊ฐ์ ์ผ๋ฐ์ ์ธ ํจ์ ์ ํผํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ์ธ์.
`act` ์ ํธ๋ฆฌํฐ๋ก React ํ ์คํธ ๋ง์คํฐํ๊ธฐ: ์ข ํฉ ๊ฐ์ด๋
ํ ์คํธ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ ์ํํธ์จ์ด์ ์ด์์ ๋๋ค. React ์ํ๊ณ์์ ์ฒ ์ ํ ํ ์คํธ๋ ์ปดํฌ๋ํธ๊ฐ ์์๋๋ก ๋์ํ๊ณ ์ ๋ขฐํ ์ ์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ ๋ฐ ๋งค์ฐ ์ค์ํฉ๋๋ค. `react-dom/test-utils`์์ ์ ๊ณตํ๋ `act` ์ ํธ๋ฆฌํฐ๋ ์ ๋ขฐํ ์ ์๋ React ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐ ํ์์ ์ธ ๋๊ตฌ์ด๋ฉฐ, ํนํ ๋น๋๊ธฐ ์ํ ์ ๋ฐ์ดํธ ๋ฐ ๋ถ์ ํจ๊ณผ(side effects)๋ฅผ ๋ค๋ฃฐ ๋ ๋์ฑ ๊ทธ๋ ์ต๋๋ค.
`act` ์ ํธ๋ฆฌํฐ๋ ๋ฌด์์ธ๊ฐ?
`act` ์ ํธ๋ฆฌํฐ๋ React ์ปดํฌ๋ํธ๊ฐ ๋จ์ธ(assertions)์ ํ ์ ์๋๋ก ์ค๋น์ํค๋ ํจ์์ ๋๋ค. ์ด๋ ๋จ์ธ์ ์์ํ๊ธฐ ์ ์ ๊ด๋ จ๋ ๋ชจ๋ ์ ๋ฐ์ดํธ์ ๋ถ์ ํจ๊ณผ๊ฐ DOM์ ์ ์ฉ๋์์์ ๋ณด์ฅํฉ๋๋ค. ํ ์คํธ๋ฅผ React์ ๋ด๋ถ ์ํ ๋ฐ ๋ ๋๋ง ํ๋ก์ธ์ค์ ๋๊ธฐํํ๋ ๋ฐฉ๋ฒ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
๋ณธ์ง์ ์ผ๋ก, `act`๋ React ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ ๋ฐํ๋ ๋ชจ๋ ์ฝ๋๋ฅผ ๊ฐ์ธ์ค๋๋ค. ์ฌ๊ธฐ์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค:
- ์ด๋ฒคํธ ํธ๋ค๋ฌ (์: `onClick`, `onChange`)
- `useEffect` ํ
- `useState` ์ธํฐ(setter)
- ์ปดํฌ๋ํธ์ ์ํ๋ฅผ ์์ ํ๋ ๊ธฐํ ๋ชจ๋ ์ฝ๋
`act`๊ฐ ์์ผ๋ฉด ํ ์คํธ๊ฐ React๊ฐ ์ ๋ฐ์ดํธ๋ฅผ ์์ ํ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ๋จ์ธ์ ์ํํ์ฌ, ๋ถ์์ ํ๊ณ ์์ธก ๋ถ๊ฐ๋ฅํ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ ์ ์์ต๋๋ค. "An update to [component] inside a test was not wrapped in act(...)."์ ๊ฐ์ ๊ฒฝ๊ณ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค. ์ด ๊ฒฝ๊ณ ๋ ํ ์คํธ๊ฐ React๊ฐ ์ผ๊ด๋ ์ํ์ ์๊ธฐ ์ ์ ๋จ์ธ์ ์ํํ๋ ์ ์ฌ์ ์ธ ๊ฒฝ์ ์กฐ๊ฑด(race condition)์ ๋ํ๋ ๋๋ค.
`act`๋ ์ ์ค์ํ๊ฐ?
`act`๋ฅผ ์ฌ์ฉํ๋ ์ฃผ๋ ์ด์ ๋ ํ ์คํธ ์ค์ React ์ปดํฌ๋ํธ๊ฐ ์ผ๊ด๋๊ณ ์์ธก ๊ฐ๋ฅํ ์ํ์ ์๋๋ก ๋ณด์ฅํ๊ธฐ ์ํจ์ ๋๋ค. ์ด๋ ๋ค์๊ณผ ๊ฐ์ ์ฌ๋ฌ ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํฉ๋๋ค:
1. ๋น๋๊ธฐ ์ํ ์ ๋ฐ์ดํธ ๋ฌธ์ ๋ฐฉ์ง
React์ ์ํ ์ ๋ฐ์ดํธ๋ ์ข ์ข ๋น๋๊ธฐ์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋๋ค. ์ฆ, ์ฆ์ ๋ฐ์ํ์ง ์์ต๋๋ค. `setState`๋ฅผ ํธ์ถํ๋ฉด React๋ ์ ๋ฐ์ดํธ๋ฅผ ์์ฝํ์ง๋ง ๋ฐ๋ก ์ ์ฉํ์ง๋ ์์ต๋๋ค. `act`๊ฐ ์์ผ๋ฉด ํ ์คํธ๋ ์ํ ์ ๋ฐ์ดํธ๊ฐ ์ฒ๋ฆฌ๋๊ธฐ ์ ์ ๊ฐ์ ๋จ์ธํ์ฌ ์๋ชป๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ ์ ์์ต๋๋ค.
์์: ์๋ชป๋ ํ ์คํธ (`act` ์ฌ์ฉ ์ ํจ)
import React, { useState } from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
test('increments the counter', () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
fireEvent.click(incrementButton);
expect(screen.getByText('Count: 1')).toBeInTheDocument(); // This might fail!
});
์ด ์์ ์์, `expect(screen.getByText('Count: 1')).toBeInTheDocument();` ๋จ์ธ์ `fireEvent.click`์ ์ํด ํธ๋ฆฌ๊ฑฐ๋ ์ํ ์ ๋ฐ์ดํธ๊ฐ ๋จ์ธ์ด ์ํ๋ ๋ ์์ ํ ์ฒ๋ฆฌ๋์ง ์์๊ธฐ ๋๋ฌธ์ ์คํจํ ์ ์์ต๋๋ค.
2. ๋ชจ๋ ๋ถ์ ํจ๊ณผ ์ฒ๋ฆฌ ๋ณด์ฅ
`useEffect` ํ ์ ์ข ์ข API์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ DOM์ ์ง์ ์ ๋ฐ์ดํธํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋ถ์ ํจ๊ณผ๋ฅผ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค. `act`๋ ํ ์คํธ๊ฐ ๊ณ์๋๊ธฐ ์ ์ ์ด๋ฌํ ๋ถ์ ํจ๊ณผ๊ฐ ์๋ฃ๋๋๋ก ๋ณด์ฅํ์ฌ ๊ฒฝ์ ์กฐ๊ฑด์ ๋ฐฉ์งํ๊ณ ์ปดํฌ๋ํธ๊ฐ ์์๋๋ก ๋์ํ๋๋ก ํฉ๋๋ค.
3. ํ ์คํธ ์ ๋ขฐ์ฑ ๋ฐ ์์ธก ๊ฐ๋ฅ์ฑ ํฅ์
ํ ์คํธ๋ฅผ React์ ๋ด๋ถ ํ๋ก์ธ์ค์ ๋๊ธฐํํจ์ผ๋ก์จ `act`๋ ํ ์คํธ๋ฅผ ๋ ์ ๋ขฐํ ์ ์๊ณ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋ง๋ญ๋๋ค. ์ด๋ ๋๋ก๋ ํต๊ณผํ๊ณ ๋๋ก๋ ์คํจํ๋ ๋ถ์์ ํ ํ ์คํธ์ ๊ฐ๋ฅ์ฑ์ ์ค์ฌ ํ ์คํธ ์ค์ํธ๋ฅผ ๋ ์ ๋ขฐํ ์ ์๊ฒ ๋ง๋ญ๋๋ค.
`act` ์ ํธ๋ฆฌํฐ ์ฌ์ฉ ๋ฐฉ๋ฒ
`act` ์ ํธ๋ฆฌํฐ๋ ์ฌ์ฉํ๊ธฐ ๊ฐ๋จํฉ๋๋ค. React ์ํ ์ ๋ฐ์ดํธ๋ ๋ถ์ ํจ๊ณผ๋ฅผ ์ ๋ฐํ๋ ๋ชจ๋ ์ฝ๋๋ฅผ `act` ํธ์ถ๋ก ๊ฐ์ธ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
์์: ์ฌ๋ฐ๋ฅธ ํ ์คํธ (`act` ์ฌ์ฉ)
import React, { useState } from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
test('increments the counter', async () => {
render(<Counter />);
const incrementButton = screen.getByText('Increment');
await act(async () => {
fireEvent.click(incrementButton);
});
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
์ด ์์ ๋ ์์ ์์๋ `fireEvent.click` ํธ์ถ์ด `act` ํธ์ถ๋ก ๊ฐ์ธ์ ธ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๋จ์ธ์ด ์ํ๋๊ธฐ ์ ์ React๊ฐ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์์ ํ ์ฒ๋ฆฌํ์์ ๋ณด์ฅํฉ๋๋ค.
๋น๋๊ธฐ `act`
`act` ์ ํธ๋ฆฌํฐ๋ ๋๊ธฐ์ ์ผ๋ก ๋๋ ๋น๋๊ธฐ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋น๋๊ธฐ ์ฝ๋(์: ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ `useEffect` ํ )๋ฅผ ๋ค๋ฃฐ ๋๋ ๋น๋๊ธฐ ๋ฒ์ ์ `act`๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
์์: ๋น๋๊ธฐ ๋ถ์ ํจ๊ณผ ํ ์คํธํ๊ธฐ
import React, { useState, useEffect } from 'react';
import { render, screen, act } from '@testing-library/react';
async function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Fetched Data');
}, 50);
});
}
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
async function loadData() {
const result = await fetchData();
setData(result);
}
loadData();
}, []);
return <div>{data ? <p>{data}</p> : <p>Loading...</p>}</div>;
}
test('fetches data correctly', async () => {
render(<MyComponent />);
// Initial render shows "Loading..."
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for the data to load and the component to update
await act(async () => {
// The fetchData function will resolve after 50ms, triggering a state update.
// The await here ensures we wait for act to complete all updates.
await new Promise(resolve => setTimeout(resolve, 0)); // A small delay to allow act to process.
});
// Assert that the data is displayed
expect(screen.getByText('Fetched Data')).toBeInTheDocument();
});
์ด ์์ ์์ `useEffect` ํ ์ ๋น๋๊ธฐ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. `act` ํธ์ถ์ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ๊ฐ์ธ๋ ๋ฐ ์ฌ์ฉ๋์ด ๋จ์ธ์ด ์ํ๋๊ธฐ ์ ์ ์ปดํฌ๋ํธ๊ฐ ์์ ํ ์ ๋ฐ์ดํธ๋์์์ ๋ณด์ฅํฉ๋๋ค. `await new Promise` ๋ผ์ธ์ `useEffect` ํ ๋ด์ `setData` ํธ์ถ์ ์ํด ํธ๋ฆฌ๊ฑฐ๋ ์ ๋ฐ์ดํธ๋ฅผ `act`๊ฐ ์ฒ๋ฆฌํ ์๊ฐ์ ์ฃผ๊ธฐ ์ํด ํ์ํ๋ฉฐ, ํนํ ์ค์ผ์ค๋ฌ๊ฐ ์ ๋ฐ์ดํธ๋ฅผ ์ง์ฐ์ํฌ ์ ์๋ ํ๊ฒฝ์์ ๊ทธ๋ ์ต๋๋ค.
`act` ์ฌ์ฉ์ ์ํ ๋ชจ๋ฒ ์ฌ๋ก
`act` ์ ํธ๋ฆฌํฐ๋ฅผ ์ต๋ํ ํ์ฉํ๋ ค๋ฉด ๋ค์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด์ญ์์ค:
1. ๋ชจ๋ ์ํ ์ ๋ฐ์ดํธ ๊ฐ์ธ๊ธฐ
React ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ ๋ฐํ๋ ๋ชจ๋ ์ฝ๋๊ฐ `act` ํธ์ถ๋ก ๊ฐ์ธ์ ธ ์๋์ง ํ์ธํ์ญ์์ค. ์ฌ๊ธฐ์๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ, `useEffect` ํ , `useState` ์ธํฐ๊ฐ ํฌํจ๋ฉ๋๋ค.
2. ๋น๋๊ธฐ ์ฝ๋์ ๋น๋๊ธฐ `act` ์ฌ์ฉํ๊ธฐ
๋น๋๊ธฐ ์ฝ๋๋ฅผ ๋ค๋ฃฐ ๋๋ ๋น๋๊ธฐ ๋ฒ์ ์ `act`๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๊ฐ ๊ณ์๋๊ธฐ ์ ์ ๋ชจ๋ ๋ถ์ ํจ๊ณผ๊ฐ ์๋ฃ๋๋๋ก ํ์ญ์์ค.
3. ์ค์ฒฉ๋ `act` ํธ์ถ ํผํ๊ธฐ
`act` ํธ์ถ์ ์ค์ฒฉํ์ง ๋ง์ญ์์ค. ์ค์ฒฉ์ ์๊ธฐ์น ์์ ๋์์ ์ ๋ฐํ๊ณ ํ ์คํธ ๋๋ฒ๊น ์ ๋ ์ด๋ ต๊ฒ ๋ง๋ค ์ ์์ต๋๋ค. ์ฌ๋ฌ ์์ ์ ์ํํด์ผ ํ๋ ๊ฒฝ์ฐ, ๋ชจ๋ ๋จ์ผ `act` ํธ์ถ๋ก ๊ฐ์ธ์ญ์์ค.
4. ๋น๋๊ธฐ `act`์ ํจ๊ป `await` ์ฌ์ฉํ๊ธฐ
๋น๋๊ธฐ ๋ฒ์ ์ `act`๋ฅผ ์ฌ์ฉํ ๋๋ ํญ์ `await`๋ฅผ ์ฌ์ฉํ์ฌ ํ ์คํธ๊ฐ ๊ณ์๋๊ธฐ ์ ์ `act` ํธ์ถ์ด ์๋ฃ๋๋๋ก ํ์ญ์์ค. ์ด๋ ๋น๋๊ธฐ ๋ถ์ ํจ๊ณผ๋ฅผ ๋ค๋ฃฐ ๋ ํนํ ์ค์ํฉ๋๋ค.
5. ๊ณผ๋ํ๊ฒ ๊ฐ์ธ์ง ์๊ธฐ
์ํ ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ์ธ๋ ๊ฒ์ด ์ค์ํ์ง๋ง, ์ํ ๋ณ๊ฒฝ์ด๋ ๋ถ์ ํจ๊ณผ๋ฅผ ์ง์ ์ ๋ฐํ์ง ์๋ ์ฝ๋๋ ๊ฐ์ธ์ง ๋ง์ญ์์ค. ๊ณผ๋ํ๊ฒ ๊ฐ์ธ๋ฉด ํ ์คํธ๊ฐ ๋ ๋ณต์กํด์ง๊ณ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง ์ ์์ต๋๋ค.
6. `flushMicrotasks`์ `advanceTimersByTime` ์ดํดํ๊ธฐ
ํน์ ์๋๋ฆฌ์ค, ํนํ ๋ชจ์ ํ์ด๋จธ๋ ํ๋ก๋ฏธ์ค๋ฅผ ๋ค๋ฃฐ ๋, React๊ฐ ์ ๋ฐ์ดํธ๋ฅผ ์ฆ์ ์ฒ๋ฆฌํ๋๋ก ๊ฐ์ ํ๊ธฐ ์ํด `act(() => jest.advanceTimersByTime(time))` ๋๋ `act(() => flushMicrotasks())`๋ฅผ ์ฌ์ฉํด์ผ ํ ์๋ ์์ต๋๋ค. ์ด๋ ๋ ๊ณ ๊ธ ๊ธฐ์ ์ด์ง๋ง, ๋ณต์กํ ๋น๋๊ธฐ ์๋๋ฆฌ์ค์ ์ ์ฉํ ์ ์์ต๋๋ค.
7. `@testing-library/user-event`์ `userEvent` ์ฌ์ฉ ๊ณ ๋ ค
`fireEvent` ๋์ `@testing-library/user-event`์ `userEvent`๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณด์ธ์. `userEvent`๋ ์ค์ ์ฌ์ฉ์ ์ํธ ์์ฉ์ ๋ ์ ํํ๊ฒ ์๋ฎฌ๋ ์ด์ ํ๋ฉฐ, ์ข ์ข ๋ด๋ถ์ ์ผ๋ก `act` ํธ์ถ์ ์ฒ๋ฆฌํ์ฌ ๋ ๊นจ๋ํ๊ณ ์ ๋ขฐํ ์ ์๋ ํ ์คํธ๋ฅผ ๋ง๋ญ๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
import React, { useState } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function MyComponent() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<input type="text" value={value} onChange={handleChange} />
);
}
test('updates the input value', async () => {
render(<MyComponent />);
const inputElement = screen.getByRole('textbox');
await userEvent.type(inputElement, 'hello');
expect(inputElement.value).toBe('hello');
});
์ด ์์ ์์ `userEvent.type`์ ํ์ํ `act` ํธ์ถ์ ๋ด๋ถ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ํ ์คํธ๋ฅผ ๋ ๊นจ๋ํ๊ณ ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
์ผ๋ฐ์ ์ธ ํจ์ ๊ณผ ์ด๋ฅผ ํผํ๋ ๋ฐฉ๋ฒ
`act` ์ ํธ๋ฆฌํฐ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ด์ง๋ง, ์ผ๋ฐ์ ์ธ ํจ์ ๊ณผ ์ด๋ฅผ ํผํ๋ ๋ฐฉ๋ฒ์ ์๋ ๊ฒ์ด ์ค์ํฉ๋๋ค:
1. ์ํ ์ ๋ฐ์ดํธ ๊ฐ์ธ๊ธฐ ์์ด๋ฒ๋ฆฌ๊ธฐ
๊ฐ์ฅ ํํ ํจ์ ์ ์ํ ์ ๋ฐ์ดํธ๋ฅผ `act` ํธ์ถ๋ก ๊ฐ์ธ๋ ๊ฒ์ ์๋ ๊ฒ์ ๋๋ค. ์ด๋ ๋ถ์์ ํ ํ ์คํธ์ ์์ธก ๋ถ๊ฐ๋ฅํ ๋์์ผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค. ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ ๋ฐํ๋ ๋ชจ๋ ์ฝ๋๊ฐ `act`๋ก ๊ฐ์ธ์ ธ ์๋์ง ํญ์ ๋ค์ ํ์ธํ์ญ์์ค.
2. ๋น๋๊ธฐ `act` ์๋ชป ์ฌ์ฉํ๊ธฐ
๋น๋๊ธฐ ๋ฒ์ ์ `act`๋ฅผ ์ฌ์ฉํ ๋๋ `act` ํธ์ถ์ `await`ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๊ทธ๋ ๊ฒ ํ์ง ์์ผ๋ฉด ๊ฒฝ์ ์กฐ๊ฑด๊ณผ ์๋ชป๋ ๊ฒฐ๊ณผ๋ก ์ด์ด์ง ์ ์์ต๋๋ค.
3. `setTimeout` ๋๋ `flushPromises`์ ๊ณผ๋ํ๊ฒ ์์กดํ๊ธฐ
`setTimeout`์ด๋ `flushPromises`๊ฐ ๋๋ก๋ ๋น๋๊ธฐ ์ํ ์ ๋ฐ์ดํธ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ง๋ง, ๋๋ฌผ๊ฒ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ, `act`๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋ ๊ฒ์ด ํ ์คํธ์ ์ ๋ขฐ์ฑ์ ๋ณด์ฅํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ๋๋ค.
4. ๊ฒฝ๊ณ ๋ฌด์ํ๊ธฐ
"An update to [component] inside a test was not wrapped in act(...)."์ ๊ฐ์ ๊ฒฝ๊ณ ๋ฅผ ๋ณด๋ฉด ๋ฌด์ํ์ง ๋ง์ญ์์ค! ์ด ๊ฒฝ๊ณ ๋ ํด๊ฒฐํด์ผ ํ ์ ์ฌ์ ์ธ ๊ฒฝ์ ์กฐ๊ฑด์ ๋ํ๋ ๋๋ค.
๋ค์ํ ํ ์คํธ ํ๋ ์์ํฌ์์์ ์์
`act` ์ ํธ๋ฆฌํฐ๋ ์ฃผ๋ก React์ ํ ์คํธ ์ ํธ๋ฆฌํฐ์ ๊ด๋ จ์ด ์์ง๋ง, ๊ทธ ์์น์ ์ฌ์ฉ ์ค์ธ ํน์ ํ ์คํธ ํ๋ ์์ํฌ์ ๊ด๊ณ์์ด ์ ์ฉ๋ฉ๋๋ค.
1. Jest ๋ฐ React Testing Library์ ํจ๊ป `act` ์ฌ์ฉํ๊ธฐ
์ด๊ฒ์ด ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์๋๋ฆฌ์ค์ ๋๋ค. React Testing Library๋ ์ ์ ํ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ณด์ฅํ๊ธฐ ์ํด `act` ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.
import React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react';
// Component and test (as shown previously)
2. Enzyme๊ณผ ํจ๊ป `act` ์ฌ์ฉํ๊ธฐ
Enzyme์ ๋ ๋ค๋ฅธ ์ธ๊ธฐ ์๋ React ํ ์คํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด์ง๋ง, React Testing Library๊ฐ ์ธ๊ธฐ๋ฅผ ์ป์ผ๋ฉด์ ์ฌ์ฉ ๋น๋๊ฐ ์ค์ด๋ค๊ณ ์์ต๋๋ค. Enzyme๊ณผ ํจ๊ป `act`๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ ํ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ณด์ฅํ ์ ์์ต๋๋ค.
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
// Example component (e.g., Counter from previous examples)
it('increments the counter', () => {
const wrapper = mount(<Counter />);
const button = wrapper.find('button');
act(() => {
button.simulate('click');
});
wrapper.update(); // Force re-render
expect(wrapper.find('p').text()).toEqual('Count: 1');
});
์ฐธ๊ณ : Enzyme์ ์ฌ์ฉํ ๋๋ `act` ํธ์ถ ํ์ ๊ฐ์ ๋ก ์ฌ๋ ๋๋งํ๊ธฐ ์ํด `wrapper.update()`๋ฅผ ํธ์ถํด์ผ ํ ์๋ ์์ต๋๋ค.
๋ค์ํ ๊ธ๋ก๋ฒ ์ปจํ ์คํธ์์์ `act`
`act` ์ฌ์ฉ ์์น์ ๋ณดํธ์ ์ด์ง๋ง, ์ค์ ์ ์ฉ์ ์ ์ธ๊ณ ์ฌ๋ฌ ๊ฐ๋ฐ ํ์ด ์ฌ์ฉํ๋ ํน์ ํ๊ฒฝ ๋ฐ ๋๊ตฌ์ ๋ฐ๋ผ ์ฝ๊ฐ์ฉ ๋ค๋ฅผ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด:
- TypeScript ์ฌ์ฉ ํ: `@types/react-dom`์์ ์ ๊ณตํ๋ ํ์ ์ `act`๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉ๋๋๋ก ๋ณด์ฅํ๊ณ ์ ์ฌ์ ์ธ ๋ฌธ์ ์ ๋ํ ์ปดํ์ผ ํ์ ๊ฒ์ฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- CI/CD ํ์ดํ๋ผ์ธ ์ฌ์ฉ ํ: `act`๋ฅผ ์ผ๊ด๋๊ฒ ์ฌ์ฉํ๋ฉด ํ ์คํธ์ ์ ๋ขฐ์ฑ์ด ๋ณด์ฅ๋๊ณ ์ธํ๋ผ ์ ๊ณต์ ์ฒด(์: GitHub Actions, GitLab CI, Jenkins)์ ๊ด๊ณ์์ด CI/CD ํ๊ฒฝ์์ ๊ฐ์ง ์คํจ(spurious failures)๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
- ๊ตญ์ ํ(i18n) ์์ ํ: ํ์งํ๋ ์ฝํ ์ธ ๋ฅผ ํ์ํ๋ ์ปดํฌ๋ํธ๋ฅผ ํ ์คํธํ ๋, ํ์งํ๋ ๋ฌธ์์ด์ ๋ก๋ํ๊ฑฐ๋ ์ ๋ฐ์ดํธํ๋ ๊ฒ๊ณผ ๊ด๋ จ๋ ๋ชจ๋ ๋น๋๊ธฐ ์ ๋ฐ์ดํธ๋ ๋ถ์ ํจ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด `act`๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉ๋์๋์ง ํ์ธํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๊ฒฐ๋ก
`act` ์ ํธ๋ฆฌํฐ๋ ์ ๋ขฐํ ์ ์๊ณ ์์ธก ๊ฐ๋ฅํ React ํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐ ํ์์ ์ธ ๋๊ตฌ์ ๋๋ค. ํ ์คํธ๊ฐ React์ ๋ด๋ถ ํ๋ก์ธ์ค์ ๋๊ธฐํ๋๋๋ก ๋ณด์ฅํจ์ผ๋ก์จ, `act`๋ ๊ฒฝ์ ์กฐ๊ฑด์ ๋ฐฉ์งํ๊ณ ์ปดํฌ๋ํธ๊ฐ ์์๋๋ก ๋์ํ๋๋ก ๋์ต๋๋ค. ์ด ๊ฐ์ด๋์ ์ค๋ช ๋ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด `act` ์ ํธ๋ฆฌํฐ๋ฅผ ๋ง์คํฐํ๊ณ ๋ ๊ฒฌ๊ณ ํ๊ณ ์ ์ง๋ณด์ ๊ฐ๋ฅํ React ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ ์ ์์ต๋๋ค. ๊ฒฝ๊ณ ๋ฅผ ๋ฌด์ํ๊ณ `act` ์ฌ์ฉ์ ๊ฑด๋๋ฐ๋ฉด ๊ฐ๋ฐ์์ ์ดํด๊ด๊ณ์์๊ฒ ๊ฑฐ์ง๋ง์ ํ๋ ํ ์คํธ ์ค์ํธ๊ฐ ๋ง๋ค์ด์ ธ ํ๋ก๋์ ์์ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ํญ์ `act`๋ฅผ ์ฌ์ฉํ์ฌ ์ ๋ขฐํ ์ ์๋ ํ ์คํธ๋ฅผ ๋ง๋์ญ์์ค.