컴포넌트 상태 변화 테스트

고기호·2024년 8월 30일
1

테스트

목록 보기
2/3

컴포넌트 상태 변화 테스트


Jest와 React-Testing-Library를 이용해서 컴포넌트 렌더링 테스트를 진행한다.

다음과 같은 스탭으로 점진적으로 발전시키면서 진행한다.

  1. 상태 초기화 테스트
  2. 상태 변화에 따른 UI 변경 테스트
  3. 사용자 입력에 따른 상태 변화 테스트

Step 1. 상태 초기화 테스트


컴포넌트 코드

import { useState } from 'react';

export default function ToggleButton() {
  const [isOn] = useState(false);

  return <button>{isOn ? 'ON' : 'OFF'}</button>;
}

테스트 코드

import { render, screen } from '@testing-library/react';
import ToggleButton from './UserProfile';

// describe: 테스트를 그룹화한다.
describe('토글 버튼 컴포넌트', () => {
  test('초기 상태가 false인 경우 OFF를 렌더링한다.', () => {
    render(<ToggleButton />);

    const buttonElement = screen.getByRole('button');

    expect(buttonElement).toHaveTextContent('OFF');
  });
});
  1. screen.getByRole로 요소를 찾는다
  2. expect.toHaveTextContent로 textContent가 OFF인지 테스트한다

결과

Step 2. 상태 변화에 따른 UI 변경 테스트


컴포넌트 코드

import { useState } from 'react';

export default function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  const toggle = () => {
    setIsOn((prev) => !prev);
  };

  return <button onClick={toggle}>{isOn ? 'ON' : 'OFF'}</button>;
}

테스트 코드 및 에러 발생

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ToggleButton from './ToggleButton';

// describe: 테스트를 그룹화한다.
describe('토글 버튼 컴포넌트', () => {
  test('초기 상태가 false인 경우 OFF이 렌더링된다.', () => {
    render(<ToggleButton />);

    const buttonElement = screen.getByRole('button');

    expect(buttonElement).toHaveTextContent('OFF');
  });

  test('버튼을 클릭하면 ON이 렌더링된다.', () => {
    render(<ToggleButton />);

    const buttonElement = screen.getByRole('button');

    // userEvent: 사용자의 행동을 시뮬레이션한다.
    userEvent.click(buttonElement);
    expect(buttonElement).toHaveTextContent('ON');
  });
});

위와 같이 테스트 코드를 짜면 에러가 발생하는데

textContent가 ON이어야 하는데 테스트 결과 OFF를 받는다.

현재 상태가 useState로 관리되기 때문에 상태 변화가 비동기적으로 이루어 지기 때문이다.

따라서 이런 경우 waitFor, findBy, waitForElementToBeRemoved 같은 비동기 테스트를 위한 메서드를 사용해야한다.

수정된 테스트 코드

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ToggleButton from './ToggleButton';

// describe: 테스트를 그룹화한다.
describe('토글 버튼 컴포넌트', () => {
  test('초기 상태가 false인 경우 OFF이 렌더링된다.', () => {
    render(<ToggleButton />);

    const buttonElement = screen.getByRole('button');

    expect(buttonElement).toHaveTextContent('OFF');
  });

  test('버튼을 클릭하면 ON이 렌더링된다.', async () => {
    render(<ToggleButton />);

    // userEvent: 사용자의 행동을 시뮬레이션한다.
    await userEvent.click(screen.getByRole('button'));

    // 현재 컴포넌트의 경우 상태 변화가 useState를 통해 이루어져 비동기적으로 이루어진다.
    // 따라서 이런 경우 비동기 테스트를 위한 메서드를 사용해야 한다.
    // waitFor, findBy, waitForElementToBeRemoved 등의 메서드를 사용할 수 있다.
    const buttonElement = await screen.findByRole('button');

    expect(buttonElement).toHaveTextContent('ON');
  });
});

결과

Step 3. 사용자 입력에 따른 상태 변화 테스트


컴포넌트 코드

import { useState } from 'react';

export default function ToggleButton() {
  const [isOn, setIsOn] = useState(false);
  const [name, setName] = useState('');

  const toggle = () => {
    setIsOn((prev) => !prev);
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setName(e.target.value);
  };

  return (
    <div>
      <button onClick={toggle}>{isOn ? 'ON' : 'OFF'}</button>
      <input type='text' aria-label='Name' value={name} onChange={handleChange} />
      <p>{name}</p>
    </div>
  );
}

테스트 코드

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ToggleButton from './ToggleButton';

// describe: 테스트를 그룹화한다.
describe('토글 버튼 컴포넌트', () => {
  test('초기 상태가 false인 경우 OFF이 렌더링된다.', () => {
    render(<ToggleButton />);
    const buttonElement = screen.getByRole('button');
    expect(buttonElement).toHaveTextContent('OFF');
  });

  test('버튼을 클릭하면 ON이 렌더링된다.', async () => {
    render(<ToggleButton />);
    await userEvent.click(screen.getByRole('button'));
    const buttonElement = await screen.findByRole('button');
    expect(buttonElement).toHaveTextContent('ON');
  });

  test('입력값이 변경되면 입력값이 렌더링된다.', async () => {
    render(<ToggleButton />);
    const inputElement = screen.getByLabelText('Name');
    await userEvent.type(inputElement, 'Hello, world!');
    const displayElement = await screen.findByText('Hello, world!');
    expect(displayElement).toBeInTheDocument();
  });
});
  1. screen.getByLabelText()를 이용해 input요소를 가져온다.
  2. userEvent.type()를 이용해 입력 상태를 변화시킨다. 이때 입력이 비동기적으로 이루어지므로 await를 사용한다.
  3. 상태 변화시 p태그의 textContent가 비동기적으로 변화하기 때문에 screen.findByText()를 이용한다. 이때 ‘Hello, world’를 넣어서 p태그의 textContent가 ‘Hello, world’되는 것을 기다린다.
  4. expect.toBeInTheDocument()로 렌더링됐는지 테스트한다.

결과

Reference


GPT를 이용한 예제 코드 실습

https://www.daleseo.com/react-testing-library-async/

profile
웹 개발자 고기호입니다.

0개의 댓글