본문 바로가기

온라인 강의(유데미, 인프런 등)/React 완벽 가이드(유데미)

[test] react 테스트 간단 개념 정리

반응형

Manual Test

브라우저에서 코드 미리 보며 테스트하는 것으로 매우 중요하다. 하지만 가능한 모든 조합과 시나리오를 테스트하는 것은 어렵기 때문에 오류 발생 가능성이 있다.

 

Automatical Test

코드를 테스트하는 코드로 앱의 개별 구성 요소를 테스트한다. 매우 기술적인 요소지만 모든 구성 요소를 한 번에 테스트할 수 있다.

1. Unit Tests

가장 작은 단위에 대한 테스트를 작성하는 것이다. 개별 구성 요소(함수, 컴포넌트)를 격리하여 테스트한다. 프로젝트에는 일반적으로 수십 또는 수 백 개의 단위 테스트가 포함된다. 가장 흔하고 가장 중요한 test이다. 모든 개별 단위를 각자 테스트하면 전체 애플리케이션도 동작한다는 아이디어로, 전체 애플리케이션이 실제로 작동하는지 확인하기 위해 Integration Tests를 진행한다.

2. Integration Tests

여러 구성요소의 조합을 test한다(예를 들어 여러 구성요소가 함께 작동되는지) . 프로젝트에는 일반적으로 몇 가지 통합 테스트가 포함된다. 중요한 테스트이지만 대부분 unit test에 집중한다.

3. End-to-End(e2e) Tests

구간 테스트로, 사용자가 경험하는 것처럼 앱에서 전체 시나리오(전체 워크플로)를 테스트한다. 실제로 사람이 웹사이트에서 실행하는 작업을 수행하는 것을 목표로 한다. 중요한 test이지만 수동으로도 수행할 수 있다.

 

테스트 대상 및 테스트 방법

무엇을 테스트할까? 서로 다른 구성 요소를 테스트해야 한다. 작고, 집중된 주요 사항만 테스트한다.

어떻게? 발생할 수 있는 성공 및 오류 사례를 테스트한다. 드물지만 가능한 시나리오와 결과도 테스트해야 한다.

필요 도구? CRA 시 이미 설정되어 있다.

  • Jest : 테스트를 실행하고 결과를 주장하기 위한 도구
  • React Testing Library : 앱 / 구성 요소를 시뮬레이션하는 도구

https://bbeeyaks-moment.tistory.com/entry/section4unit7Testing-TDD

 

section4/Unit7/[Testing] TDD

TDD 방법론 TDD(Test-driven Development)는 코드를 작성하기 전에 테스트를 쓰는 소프트웨어 개발 방법론이다. 다시 말해, 개발자 자신이 바람직하다고 생각하는 코드의 결과를 미리 정의하고, 이것을

bbeeyaks-moment.tistory.com

 

테스트 실행

테스트를 실행하기 위해 자동으로 생성된 setupTest.js 파일을 삭제하면 안 된다. 컴포넌트 테스트를 위해 컴포넌트 이름.test.js 파일을 생성해 준다.

  • Arrange : 테스트 데이터 셋업, 테스트 조건 및 테스트 환경 설정
  • Act : 테스트 필요한 로직 실행(ex 실행함수)
  • Assert : 실행 결과와 예상 결과 비교

테스트 실행 코드

npm test -> 'a'키 누르면 모든 .test.js 테스트 코드 실행됨

 

사용자 상호작용 및 state 테스트

[예시]

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

// suite : describe => 테스트 그룹화
// 첫번째 아규먼트 : 테스트 group 설명
// 두번째 아규먼트 : 각각의 테스트
describe('Greeting component', () => {
 // 첫번째 아규먼트 : 테스트 설명
 // 두번째 아규먼트 : 실제 테스트와 코드를 포함하는 함수
  test('renders "Hello World" as a text', () => {
    // Arrange
    // 컴포넌트 엘리먼트 생성
    render(<Greeting />);

    // Act
    // ... nothing
	
    // Assert
    // 가상의 화면에 시뮬레이션
    const helloWorldElement = screen.getByText('Hello World', { exact: false });
    expect(helloWorldElement).toBeInTheDocument();
  });

  test('renders "good to see you" if the button was NOT clicked', () => {
    render(<Greeting />);

   // exact를 false로 하면 느슨하게 테스트
    const helloWorldElement = screen.getByText('good to see you', { exact: false });
    expect(helloWorldElement).toBeInTheDocument();
  });

  test('renders "Changed!" if the button was clicked', () => {
    // Arrange
    render(<Greeting />);

    // Act
    const buttonElement = screen.getByRole('button');
    userEvent.click(buttonElement);

    // Assert
    const helloWorldElement = screen.getByText('Changed!');
    expect(helloWorldElement).toBeInTheDocument();
  });

  test('does not render "good to see you" if the button was clicked', () => {
    // Arrange
    render(<Greeting />);

    // Act
    const buttonElement = screen.getByRole('button');
    userEvent.click(buttonElement);

    // Assert
    // queryByText => 엘리먼트가 찾아지지 않으면 단순히 null 반환
    const helloWorldElement = screen.queryByText('good to see you', {
      exact: false,
    });
    expect(helloWorldElement).toBeNull();
  });
});

 

연결된 컴포넌트 테스트

[예시]

render가 요구되는 컴포넌트 트리 전체를 렌더링하기 때문에 테스트는 여전히 정상 작동한다. 

// Output.js
const Output = props => {
  return <p>{props.children}</p>
};

export default Output;

// Greeting.js
import { useState } from 'react';

import Output from './Output';

const Greeting = () => {
  const [changedText, setChangedText] = useState(false);

  const changeTextHandler = () => {
    setChangedText(true);
  };

  return (
    <div>
      <h2>Hello World!</h2>
      {!changedText && <Output>It's good to see you!</Output>}
      {changedText && <Output>Changed!</Output>}
      <button onClick={changeTextHandler}>Change Text!</button>
    </div>
  );
};

export default Greeting;

 

비동기 코드 테스트

[예시]

// Async.js

import { useEffect, useState } from 'react';

const Async = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then((response) => response.json())
      .then((data) => {
        setPosts(data);
      });
  }, []);

  return (
    <div>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default Async;
// Async.test.js

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

// list item이 렌더링되었는지 테스트
describe('Async component', () => {
  // getAllByRole은 즉시 찾기 때문에 findAllByrole 사용 후 익명 함수에 async 넣어줌
  // (처음 렌더링되면 리스트가 빈 상태 => useEffect안에 fetch 로직이 완료되어 리스트 가져오면 리렌더링)
  test('renders posts if request succeeds', async () => {
    render(<Async />)

    // getByRole => 역할(html 요소)에 따라 찾기(하나를 초과하는 아이템이 있다면 작동 안함)
    // getAllByRole => 역할에 따라 찾기(복수의 아이템이 있어도 작동).
    // findAllByRole => 역할에 따라 찾기(복수의 아이템이 있어도 작동). promise 반환함.
    const listItemElements = await screen.findAllByRole('listitem');
    expect(listItemElements).not.toHaveLength(0);
  });
});

 

모의 작업(mock 함수)

테스트를 작성할 떄는 내가 작성하지 않은 코드를 테스트하면 안된다. fetch 코드는 브라우저가 작성한 것이기 때문에 fetch가 요청을 성공적으로 전송하는지 테스트하는 것이 아닌 전송된 요청에 따라 컴포넌트가 올바르게 작동되는지 테스트해야 한다. 그러므로 fetch 함수를 mock 함수로 덮어써야 한다.

mock 함수란 내장 함수를 덮어쓰는 더미 함수로 우리가 원하는 바를 행하면서 진짜 요청을 보내지 않는 것이다.

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

describe('Async component', () => {
  test('renders posts if request succeeds', async () => {
    // fn => mock 함수(더미 함수)를 만들어줌
    window.fetch = jest.fn();
    // mockResolvedValueOnce => fetch 함수가 호출되었을 때 결정되어야 하는 값을 설정하게 해줌(ex : .json())
    window.fetch.mockResolvedValueOnce({
    // .json()도 promise를 반환하기 때문에 async 사용
      json: async () => [{ id: 'p1', title: 'First post' }],
    });
    render(<Async />);

    const listItemElements = await screen.findAllByRole('listitem');
    expect(listItemElements).not.toHaveLength(0);
  });
});

 

+ 간단히 리액트 테스트에 대한 소개 개념만 정리했으므로 추후 테스트 코드 작성 시 Jest 공홈, React Testing Library 공홈(특히 Ecosystem 부분) 찾아보기!

+ react testing library react hooks 확장 :  리액트 훅, 커스텀 훅 쉽게 테스트하게 도와줌

반응형