본문 바로가기
공부/React & React Native

React Testing에 대하여

by Piva 2023. 11. 22.
  • Udemy의 React - The Complete Guide 강의의 Section 27: Testing React Apps(Unit Tests)를 듣고 공부한 내용을 간략히 정리.

Automating Testing (테스트 자동화)

테스트는 왜 해야할까?

  • 개발자가 개발하는 도중, 수동으로 작동 등을 확인하는 것은 '수동 테스트(Manual Test)'이다.
    • 다만, 테스트를 수작업으로 진행할 경우 가능한 모든 테스트 케이스(시나리오)를 테스트할 수 없기 때문에 예상치 못한 에러가 발생할 수 있다.
  • 이에 반해, 자동화된 테스트는 모든 시나리오를 자동적으로 테스트할 수 있다. 개발자가 하나하나 손으로 하는 것보다 시간도 절약할 수 있다.
  • 따라서 수동 테스트만 진행하는 것 보다, 자동 테스트를 같이 병행함으로써 에러를 조기에 발견하고, 더 좋은 코드를 완성할 확률을 높일 수 있다.

 

테스트의 종류

1. 유닛 테스트

  • 가장 작은, 개별적 단위에 대해 테스트를 진행하는 것.
    ex) 함수, (React 의 경우) 컴포넌트 등...
  • 그렇기에 가장 양이 많고, 가장 중요시됨.
    → 어플리케이션의 가장 작은 단위들에 문제가 존재하지 않는다면, 어플리케이션 그 자체도 문제가 없을 것이라는 생각에 기반.

 

2. 통합 테스트

  • 여러 블록 간의 조합을 테스트하는 것.
  • 중요하지만, 대부분의 경우 유닛 테스트를 좀더 우선시한다고 함.

 

3. E2E(End-to-End) 테스트

  • 완전한 시나리오/유저 플로우를 테스트하는 것.
  • 중요는 하지만, 수동으로도 이러한 테스트를 진행할 수 있음 + 대체로 유닛이나 통합 단위에서 문제가 없을 경우, 전체 어플리케이션도 문제가 없을 것이라 예상할 수 있기 때문의 앞의 2개보다 중요도가 떨어지는 듯.

 

테스트하는 법

: 리액트의 경우, '테스트를 진행하고 결과를 도출해낼 도구' + '컴포넌트와 리액트의 앱 렌더링을 시뮬레이션 할 도구'가 필요함.

  • 테스트 진행 도구 → Jest
  • 시뮬레이션 도구 → React Testing Library

⇒ CRA(Create React App)으로 프로젝트를 생성할 경우, 위의 두 가지 모두 기본적으로 프로젝트에 세팅되어있음.

 

 

테스트 코드 작성

※ 일반적인 컨벤션은 테스트 코드를 테스트 대상과 가능한 한 가까이 두는 것.

 

테스트 작성 시 고려점(3A)

  1. Arrange: 테스트 데이터/조건/환경을 구성
  2. Act: 테스트되어야하는 로직을 실행
  3. Assertion: 테스트 결과를 예상 결과와 비교

 

※ 예제

import TestComponent from "./TestComponent";
import { render, screen } from "@testing-library/react";

// test(): 테스트를 실행하는 함수, *.test.js에서 테스트를 실행하기 위해 필요한 최소한의 함수
// 첫 번째 인자: 테스트 코드에 대한 설명
// 두 번째 인자: 익명 함수 형태의 테스트 코드

test('renders Test component as a text', () => {
  // Arrange 단계 => 테스트할 컴포넌트를 가상 스크린에 렌더링
  render(<TestComponent />);

  // Assertion 단계 => 예상 결과(TestComponent가 Test component를 띄움)
  const randomElement = screen.getByText("Test component");
  
  // TestComponent가 document내에 존재할 것을 예상
  expect(randomElement).toBeInTheDocument();
});

 

→ 작성한 코드는 yarn test / npm test 등으로 테스트 가능

 

 

Test VS Test Suites

  • Test Suite: 서로 연관 있는 테스트 케이스들을 묶어놓은 것.

→ 어플리케이션의 규모가 커질 수록, 서로 연관이 있는(혹은 같은 컴포넌트의) 테스트들을 하나의 Test Suite로 묶는 것이 좋음.

→ Test Suite는 describe()를 통해 선언할 수 있음.

describe('Some description for test suite', () => {
  // 여기에 다른 묶고자 하는 테스트들을 작성함.
  test('Some test', () => {});
});

 

 

테스트에서 이벤트 트리거하기

 @testing-library/user-event 에서 userEvent를 임포트하여 사용.

import userEvent from "@testing-library/user-event";

// 버튼을 누르면 내부의 state가 바뀌고, Changed! 텍스트가 뜨는 Greeting 컴포넌트
// 버튼이 정상적으로 작동하는 지를 확인하기 위해, 버튼의 click이벤트를 발생시킴
test('renders Changed!', () => {
  render(<Greeting />);
  const buttonElement = screen.getByRole('button');

  // button 에 click이벤트를 트리거
  userEvent.click(buttonElement);

  const textElement = screen.getByText("Changed!", { exact: false });
  expect(textElement).toBeInTheDocument();
});

 

 

※ screen.queryBy~

  • screen.getBy~ 는 조건에 부합하는 요소를 찾지 못했을 경우 에러를 throw함.
  • 반면 screen.queryBy~ null을 반환하기 때문에, 특정 컴포넌트가 렌더링 되지 않는 상황을 테스트할 때 사용할 수 있음.

※ 참고

 

비동기 코드 테스트

  • 위에서 사용한 getBy~ 의 경우, 비동기 코드 실행을 기다리지 않고 바로 조건에 부합하는 요소를 찾기 때문에 비동기 코드 테스트에 사용하기 어려움.
  • findBy~ 를 사용하면 프로미스를 반환, 해당 프로미스가 성공할 때 까지 스크린을 계속해서 재평가함.
    → 즉, 특정 로직이 바로 실행되지 않고 기다려야 하는 상황에서 사용하기 적합함.
// test 함수는 async 함수 또한 받을 수 있음
test('renders posts if request succeeds', async () => {
  render(<Async />);
  
  const listItemElements = await screen.findAllByRole('listitem');
  expect(listItemElements).not.toHaveLength(0);
});
  • 다만, 위의 방법은 실제로 서버에 요청을 보내기 때문에 좋은 방법이 아님.
    → 다수의 테스트 코드가 존재할 경우, 서버에 너무 많은 요청을 보낼 수 있기 때문.
    ⇒ 따라서, mock을 사용하는 방법이 존재.

 

mock

  • 서버에 실제 요청을 보내지 않음 + 서버와 통신하는 함수의 기능을 테스트하려는 목적이 없을 경우 사용할 수 있음.
  • 일종의 더미 함수로, 원래 컴포넌트 속 함수를 대체함.
// Async.js
/*
    fetch를 통해 웹에서 데이터 리스트를 받아옴
    받아온 데이터를 리스트 형태로 렌더
*/


// Async.test.js
test('renders posts if request succeeds', async () => {
  window.fetch = jest.fn(); // jest.fn(): window의 fetch를 mock함수로 대체함\
  
  // 해당 mock 함수가 resolve되면 설정한 값을 반환함
  window.fetch.mockResolvedValueOnce({
    json: async () => [{id: 'p1', title: 'first post'}],
  });

  render(<Async />);
  
  const listItemElements = await screen.findAllByRole('listitem');
  expect(listItemElements).not.toHaveLength(0);
});

  • 테스트 코드는 딱 한 번 작성해본 경험밖에 없어서, 기초적인 이해가 필요하겠다는 판단 하에 들었다. 
  • 위의 내용은 정말 간단한 내용만 포함되어있으므로, 나중에 Jest와 testing library의 공식 문서를 통해 조금 더 공부하고 실제 프로젝트에 적용하는 연습을 해보고 싶다.