React가 브라우저에서 동작하는 것과 흡사한 환경에서 테스트를 수행할 수 있도록, React에서는 act라는 API를 제공하고 있다.
UI 테스트를 수행하기 위해서는 렌더링, 이벤트 발생, 데이터 불러오기 등 UI와 상호작용하는 작업이 수행되어야 하는 경우가 있다. 이로 인해 발생하는 컴포넌트 렌더링이나 데이터 갱신은 동기적으로 처리될 수도 있지만, 리액트가 내부적으로 스케줄링하여 비동기적으로 처리하게 될 수도 있다. 따라서 작업의 결과가 언제 적용될 것인지는 리액트가 어떻게 동작할지에 달려 있다.
act
가 하는 일은 위와 같이 리액트의 내부 동작에 영향을 받는 코드들이 assertion을 수행하기 전에 모두 완료되게 하는 것이다. 리액트 동작과 관련 있는 코드를 act 함수로 감싸서 호출하면 act는 Promise를 반환하고, 이 Promise는 act가 감싸고 있는 코드가 모두 처리되어 DOM에 반영되기 직전에 resolve된다.
React Testing Library의 경우
React Testing Library는 React에서 제공하는 act를 감싼 같은 이름의 함수를 제공하고 있다. 뿐만 아니라, 제공하는 API 중 render
, fireEvent
등의 함수에서도 내부적으로 act를 사용해 구현하고 있다. 따라서 단순 React API를 사용했다면 act로 감싸야 하겠지만, React Testing Library를 사용하는 경우 render 호출이나 이벤트 발생 코드는 act로 감싸지 않아도 된다.
그렇지만 React Testing Library를 거치지 않고 상태 변경이 일어나는 경우에는 코드 호출 시 act로 감싸 주어야 한다.
1const useBoolean = (initialState = false) => {2 const [value, setValue] = useState(initialState)34 const toggle = () => setValue(prev => !prev)56 return { value, setValue, toggle }7}89test('toggle을 호출하면 값이 반대가 된다', () => {10 const { result } = renderHook(() => useBoolean(false))1112 // act로 감싸주지 않으면 expect 시점에 값이 갱신되어 있지 않다13 act(() => result.current.toggle());1415 expect(result.current.value).toBe(true);16})
정리하자면 아래와 같은 기준으로 act를 감싸면 된다.
- React Testing Library가 제공하는 API - render, userEvent, fireEvent 등은 act로 감쌀 필요 없음
- 그 외에 리액트 컴포넌트 내부 상태를 변경하는 코드 - act로 감싸주어야 함