반응형

React test Warning test was not wrapped in act error 해결 방법

test was not wrapped in act
test was not wrapped in act

console.error
    Warning: An update to FileIcon inside a test was not wrapped in act(...).  

    When testing, code that causes React state updates should be wrapped into act(...):

    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

      10 |     icons
      11 |       .getClass(name)
    > 12 |       .then((k) => setKlass(k))
         |                    ^
      13 |       .catch(() => null);
      14 |   }, [name]);
      15 |

Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.609 s

Test는 통과하지만, 빨간 error 로그가 신경 쓰일 수 있는데요, 이 부분을 해결하는 2가지 방법에 대한 포스팅입니다.


테스트 코드

RepositoriesListItem에 대한 테스트 코드입니다. 테스트 안에는 renderComponent();만 있는데 위와 같은 error가 발생합니다. act 부분을 잘 처리하지 못하서 발생하는 error입니다.  log에 찍힌 대로 act에 pause를 추가하여 문제를 해결할 수도 있지만, 이 방법보다는 다른 방법이 더 좋다고 합니다. 

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import RepositoriesListItem from './RepositoriesListItem';

function renderComponent() {
  const repository = {
    full_name: 'facebook/react',
    language: 'Javascript',
    description: 'A js library',
    owner: 'facebook',
    name: 'react',
    html_url: 'https://github.com/facebook/react',
  };
  render(
    <MemoryRouter>
      <RepositoriesListItem repository={repository} />
    </MemoryRouter>
  );
}
test('shows a link to the github homepage for this repository',() => {
  renderComponent();
});

act 오류가 발생하는 이유는, test 이외의 component에 접근하고, 그 component에 useEffect가 쓰여서 Icon을 받아오는데  시간이 걸리기 때문이라고 합니다. 아래 코드를 보면 FileIcon이라는 걸 return 하는데, FileIcon 코드를 보면 getClass 부분에서 시간이 소요됩니다. 

//RepositoriesListItem.js
import { Link } from 'react-router-dom';
import FileIcon from '../tree/FileIcon';
import RepositoriesSummary from './RepositoriesSummary';

function RepositoriesListItem({ repository }) {
  const { full_name, language, description, owner, name } = repository;
  return (
    <div className="py-3 border-b flex">
      <FileIcon name={language} className="shrink w-6 pt-1" />
//FileIcon.js
import "@exuanbo/file-icons-js/dist/css/file-icons.css";
import icons from "@exuanbo/file-icons-js/dist/js/file-icons";
import { useEffect, useState } from "react";
import classNames from "classnames";

function FileIcon({ name, className }) {
  const [klass, setKlass] = useState("");

  useEffect(() => {
    icons
      .getClass(name)
      .then((k) => setKlass(k))
      .catch(() => null);
  }, [name]);

  if (!klass) {
    return null;
  }

  return (
    <i
      role="img"
      aria-label={name}
      className={classNames(className, klass)}
    ></i>
  );
}

해결 방법 findByRole 사용

문제를 해결하는 방법은 생각보다 간단합니다. findBy 함수를 사용하면 된다고 하는데요, findBy를 사용하면 기본적으로 1초를 기다리고 테스트 결과를 보기 때문에, 문제가 해결된다고 합니다. (제일 추천하는 방법)

test('shows a link to the github homepage for this repository', async () => {
  renderComponent();
  await screen.findByRole('img', { name: 'Javascript' });
});

해결 방법 mock 사용

FileIcon이 문제니, FileIcon 코드로 가지 않도록 설정하는 방법입니다. jest.mock를 생성하고, 대신하고 싶은 file 경로를 추가하고, 이 경로가 불리면 'File Icon Component'를 return 하는 mock 함수입니다. 

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import RepositoriesListItem from './RepositoriesListItem';

jest.mock('../tree/FileIcon', () => {
  //Content of FileIcon.js
  return () => {
    return 'File Icon Component';
  };
});
...
test('shows a link to the github homepage for this repository', () => {
  renderComponent();
});

이렇게 작성하면 screen.debug()를 해봤을 때, FileIcon부분이 아래처럼 보인다는 걸 볼 수 있습니다. 

... 
       <div
          class="py-3 border-b flex"
        >
          File Icon Component
          <div>
...

이상으로 act error가 발생했을 때 에러를 지울 수 있는 2가지 방법이었습니다. 

 

*udemy React Testing Library and Jest: The Complete Guide를 참고한 글입니다. 

반응형