React

React와 TDD

Mia_ 2023. 1. 31. 14:49

React 환경에서 테스트하기 

- 리액트에서 테스트는 Testing Library, Jest를 이용해서 가능

- 역할이 각각 다르기 때문에 리액트에 대한 테스트를 진행할 때는 두 라이브러리 모두 필요

- 둘 다 리액트에만 사용하는 라이브러리는 아님 뷰 등 다른 프레임워크에서도 사용 가능

 

- Testing Library에서 리액트용 React Test Libraray를 제공하고 있기 때문에 CRA를 이용해 프로젝트를 생성하면 자동으로 Testing Library 를 이용할 수 있음 

테스트를 실행하고 싶은 컴포넌트나 클릭 이벤트 등 다양한 곳에 쓸 수 있음

 

- Jest는 자바스크립트의 Testing Framework/Test Runner로써, 테스트 파일을 자동으로 찾아 테스트를 실행

→ 테스트 실행 결과가 기대만큼 올바른 값을 가지고 있는지를 함수를 이용하여 체크 → 테스트 성공 여부를 판단해줌

 

 

1. React 기본 테스트 환경 확인하기 

1.1  CAR를 이용해서  React 프로젝트를 생성

- npx create-react-app을 이용해서 리액트 프로젝트를 생성했다면 테스트 환경이 설정되어 있기 때문에 테스트가 가능함

- package.json 파일을 확인하면 @testing 이라는 접두사가 붙은 3개의 라이브러리가 아래와 같이 확인 가능 

package.json 파일

- @testing-library/jest-dom : Jest-dom 제공하는 custom matcher를 사용할 수 있게 도와줌

- @testing-library/react : 컴포넌트 요소를 찾기 위한 쿼리가 포함되어 있음

- @testing-library/user-event : click 등 사용자 이벤트에 이용됨

 

1.2 테스트 파일 확인하기

// src/App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

- test 함수는 Jest 함수로 테스트를 실행할 때 반드시 이용하는 함수임

- test 함수의 첫 번재 인자는 테스트가 어떤 내용인지 나중에 읽어도 테스트 내용을 알 수 있는 설명을 작성함

 

- 두 번째 인자는 하고자 하는 테스트를 함수의 형태로 넣음 

→ 첫번째 줄을 보면 테스트하고자 하는 컴포넌트를 render () 함수로 전달함. react-testing-library에서는 테스트를 진행할 컴포넌트를 render() 함수의 인자로 전달함

→ 두번째 줄에 있는 screen의 다양한 메소드 중 getByText() 메서드를 이용하여 render()에서 가져온 App 컴포넌트 중 "learn react"라는 문자열이 있는지 확인하여 linkElement에 할당하고 있음 ("i"는 Regular Expression으로 "I"를 붙임으로써 대소문자를 구분하지 않게 만들어 줌)

→ 세번째 줄에서는 expect 함수의 인자로 지정한 요소가 document.body에 존재하는지 toBeInTheDocument 함수를 사용하여 체크하고 있음. 여기서 tobeInTheDocument 함수는 matchers 함수라고 부름

 

- App.test.js 파일 중 이용되고 있는 test 함수, expect 함수는 Jest 함수이고

- toBeInTheDocument는 jest-dom 라이브러리에 포함된 Custom matchers임 

 

// src/setUpTest.js

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

- jest-dom은 위의 setUpTest.js 파일 내에서 import 되고 있음

- import를 삭제하면 App.test,js의 toBeInTheDocument 함수를 사용할 수 없음!

 

- Home 컴포넌트 안에 span 태그 안에 learn React 텍스트를 작성했고 i를 붙임으로서 대소문자 구별이 없게 되었음

- Home 컴포넌트는 최상위 App 컴포넌트의 하위 컴포넌트이고 App 컴포넌트는 빈 HTML 파일에 뿌려져서 document.body 안에 learn react를 가진 요소가 있게 됨

- 그래서 테스트 통과

 

1.3 간단한 테스트 만들어 보기

- 테스트를 실행하기 위해서는 파일명을 <파일명>.test.js 와 같이 생성해야 함(<파일명>.spec.js 도 가능)

- 파일명을 이렇게 작성하고 테스트 하면, Jest 가 테스트 파일로 판단하여 작동 

 

- test 함수의 첫번째 인자에 테스트에 대한 설명을, 두번째 인자에는 테스트 내용을 함수의 형태로 작성하여 테스트에 패스함

- toBe 함수는 matchers 함수 중 하나로 expect 함수에 지정한 값이 toBe 함수에 지정한 값과 일치하는지 체크함

 

- 지정한 값과 일치하지 않을 경우 테스트가 왜 실패했는지 출력 됨 

 

- test 함수 대신 it 함수를 사용해도 같은 결과가 나옴

- describe 함수를 사용하면 it 함수나 test 함수를  하나의 파일에 여러 개 포함 시킬 수 있음

- describe 함수 블록은 Test Suites라고 불리며 test/it 함수 블록은 Test(Test Case) 라고 함 

 

 

2.  직접 컴포넌트 생성하여 테스트 하기

2.1 컴포넌트 만들기 

- 이전에 CAR를 이용해 생성했던 리액트 앱에 Light 컴포넌트를 추가 해줌

Light.js
import { useState } from "react";

function Light({ name }){
  const [light, setLight] = useState(false);

  return (
    <div>
      <h1>
        {name} {light ? 'ON' : "OFF"}{' '}
      </h1>
      <button
        onClick={() => setLight(true)}
        disabled={light ? true : false}
      >ON</button>
       <button
        onClick={() => setLight(false)}
        disabled={!light ? true : false}
      >OFF</button>
    </div>
  );
}

export default Light;

 

App.js
import './App.css';
import React from 'react'
import { BrowserRouter, Routes, Route, Link } from "react-router-dom"; 
import Light from './Light';

.. 생략 ..

function App() {
  return (
    <BrowserRouter>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/mypage">MyPage</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
            <li>
              <Link to="/light">Light</Link>
            </li>
          </ul>
        </nav>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path='/mypage' element={<MyPage />}/>
          <Route path='/dashboard' element={<Dashboard />} />
          <Route path='/light' element={<Light name="전원" />} />
        </Routes>
      </div>
    </BrowserRouter>
  )
}

export default App;

 

2.2 컴포넌트 테스트하기 

-  Light.test.js 파일을 만들고 아래와 같이 작성함

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

it('renders Light Component', () => {
	render(<Light name="전원" />);
	const nameElement = screen.getByText(/전원 off/i);
	expect(nameElement).toBeInTheDocument();
})

- 위에서 했던 테스트와 거의 동일한 테스트 

- render에서 가져온 Light 컴포넌트에 전원 off 문자열이 있는지 확인

 

it('off button disabled', () => {
	render(<Light name="전원" />);
	const offButtonElement = screen.getByRole('button', { name: 'OFF' });
	expect(offButtonElement).toBeDisabled();
})

- 위의 테스트는 OFF 버튼이 disabled로 되어 있는지 matchers 함수의 toBeDisabled 함수를 이용해 테스트를 작성함

- getByRole을 이용해 button을 지정했고, 버튼이 2개이므로 옵션의 name을 이용하여 OFF 버튼을 찾음

- 현재 OFF  버튼이 disabled 상태이기 때문에 테스트 결과도 PASS로 나옴 

 

it('on button enable', () => {
  render(<Light name="전원" />);
  const onButtonElement = screen.getByRole('button', { name: 'ON' });
  expect(onButtonElement).not.toBeDisabled();
});

- 위의 테스트는 ON 버튼이 disableld가 아니라는 것을 테스트 하기 위한 것

- 간단하게 toBeDisabled 앞에 not을 붙이면 구현할 수 있음

 

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

it('change from off to on', () => {
	render(<Light name="전원" />);
	const onButtonElement = screen.getByRole('button', { name: 'ON' });
	fireEvent.click(onButtonElement);
	expect(onButtonElement).toBeDisabled();
})

- 버튼 클릭 이벤트의 유무도 테스트로 구현할 수 있음

- fiveEvent를 사용하려면 import를 하고 fiveEvent의 clike 메서드에 전달 인자로 테스트하고자 하는 요소를 전달함