RTL "Not wrapped in act() ... " 에러

JaeHong Jeong·2023년 9월 7일
0

Test

목록 보기
1/3
post-thumbnail

리액트 테스팅 공부를 하다가 해당 에러를 만나게 되었다. 테스트는 다 통과를 하지만 에러가 일어나는 이상한 현상이다.
https://velog.velcdn.com/images/bohongu/post/2f24f7ca-32b8-4baa-a194-b1b119a53f91/image.png

에러 파악

우선 해당 에러가 어떤 테스트 코드에서 일어나는 지 확인하기 위해 test.skiptest.only를 사용하여 해당 테스트 코드를 식별하였다.

test.skip("grand total starts at $0.00", () => {
    render(<OrderEntry />);
    const grandTotal = screen.getByRole("heading", {
      name: /Grand total: \$/i,
    });
    expect(grandTotal).toHaveTextContent("0.00");
  });
test.only("grand total starts at $0.00", () => {
    render(<OrderEntry />);
    const grandTotal = screen.getByRole("heading", {
      name: /Grand total: \$/i,
    });
    expect(grandTotal).toHaveTextContent("0.00");
  });

Warning : An update to Options inside a test was not warpped in act(…).

Warning : Can’t perform a React state update on an unmounted component.

위 오류는 언마운트 컴포넌트에서 리액트 상태 업데이트를 수행할 수 없다는 오류다.

원인은 대부분 테스트가 끝난 뒤에 컴포넌트가 바뀌기 때문이다. 일부 비동기 상태 업데이트가 완료되기 전에 테스트 함수가 종료된다.

위에 테스트 코드에서 렌더링하는 <OrderEntry /> 컴포넌트의 자식 컴포넌트 (<Options /> )는 axios호출, 즉 비동기 작업이 있다.

useEffect(() => {
    axios
      .get(`http://localhost:3030/${optionType}`)
      .then((response) => setItems(response.data))
      .catch((error) => {
        setError(true);
      });
  }, [optionType]);

원인

Race codition

  1. 테스트에서 컴포넌트를 렌더링한다.
  2. 컴포넌트가 네트워크 요청을 한다.
  3. 하지만 테스트는 네트워크 호출이 반환될 때까지 기다리지 않고 테스트를 끝내면 종료한다.
  4. 테스트 함수가 종료될 때 Testing Library는 정리를 하면서 컴포넌트를 언마운트한다.
  5. 그 동안에도 네트워크 호출은 반환되지 않았고 테스트가 종료되어 컴포넌트가 언마운트된 이후에 반환된다.
  6. 그때, 테스트에 고려하지 않은 무언가가 있다는 경고를 받는다.

해결

컴포넌트가 언마운트될 때 클린업 과정을 추가한다.

테스트에서 명시적으로 컴포넌트를 언마운트 한다. 그렇게 하면 컴포넌트에 수행한 작업에 따라 네트워크 호출이 취소된다.

// Options.js

useEffect(() => {
    const controller = new AbortController();
    axios
      .get(`http://localhost:3030/${optionType}`, { signal: controller.signal })
      .then((response) => setItems(response.data))
      .catch((error) => {
        setError(true);
      });
    return () => {
      controller.abort();
    };
  }, [optionType]);
// Options.test.js

test("grand total starts at $0.00", () => {
    const { unmount } = render(<OrderEntry />);
    const grandTotal = screen.getByRole("heading", {
      name: /Grand total: \$/i,
    });
    expect(grandTotal).toHaveTextContent("0.00");
    unmount();
  });

waitFor 비동기 유틸리티를 사용한다.

waitFor를 통해 컴포넌트 업데이트가 완료될때까지 기다릴 수 있다.

// Options.js

useEffect(() => {
    axios
      .get(`http://localhost:3030/${optionType}`)
      .then((response) => setItems(response.data))
      .catch((error) => {
        setError(true);
      });
  }, [optionType]);
// Options.test.js

test("grand total starts at $0.00", async () => {
    render(<OrderEntry />);
    const grandTotal = screen.getByRole("heading", {
      name: /Grand total: \$/i,
    });
    await waitFor(() => {
      expect(grandTotal).toHaveTextContent("0.00");
    });
  });
profile
반갑습니다.

0개의 댓글