React TDD - React Testing Library

박정호·2022년 8월 20일
0

TDD

목록 보기
4/6
post-thumbnail

😁 React Testing Library

  • 렌더링 결과에 조금 더 집중, DOM 위주로 진행
    -> 실제 DOM에 대해서 신경을 더 많이 쓰고, 컴포넌트의 인스턴스에 대해서 신경쓰지 않고, 실제 화면에 무엇이 보여지는지, 그리고 어떠한 이벤트가 발생했을 떄 화면에 원하는 변화가 생겼는지 확인하는 것에 조금 더 최적화(컴포넌트의 props나 state를 조회하는 일이 X)

  • 사용자 관점에서 테스팅하기가 더욱 용이

  • 컴포넌트를 리팩토링할 때 주로 내부 구조 및 네이밍은 많이 바뀌어도 실제 작동 방식은 크게 변경 X
    -> 따라서 react-testing-library가 이 점을 유의하여, 컴포넌트의 기능이 똑같이 작동한다면 컴포넌트의 내부 구현 방식이 만히 바뀌어도 테스트가 실패하지 않도록 설계

  • Enzyme에 비해 정말 필요한 기능들만 지원해주어 가볍고, 개발자들이 일관성 있고 좋은 관습을 따르는 테스트 코드를 작성할 수 있게 유도
    (Enzyme:구현 주도 테스트 , React Testing Library: 행위 주도 테스트)

  • Create React App으로 생성된 프로젝트는 즉시 React Testing Library를 지원. 그렇지 않은 경우 다음과 같이 npm을 통 해 추가 가능

  • DOM testing Library 위에 구축되어 있는 라이브러리

    공식문서 :https://testing-library.com/docs/react-testing

💡중요!
DOM(Document Object Model)

  • Document: 문서 / Object: 객체 / Model: 모델
  • DOM은 XML, HTML 문서의 각 항목을 계층으로 표현하여 생성,변형 삭제할 수 있도록 돕는 인터페이스이다. (:위키피디아)
    " HTML 요소들의 구조화된 표현이며,DOM은 HTML이 브라우저의 렌더링 엔진에 의해 분석되고 분석이 모두 끝나고난 HTMl 파일이 DOM이다.HTML은 화면에 보이고자 하는 모양과 구조를 문서로 만들어서 단순 텍스트로 구성되어있으며 DOM은 HTML문서의 내용과 구 조가 객체 모델로 변화되어 다양한 프로그램에서 사용될 수 다. HTML 문서가 유효하지 않게 작성됐을때는 브라우저가 올바르 게 교정해주며, DOM은 자바스크립트에 의해 수정될 수 있다. 하지만 HTML은 수정하지 않는다."

웹 페이지 빌드 과정(Critical Rendering Path CRP)

  • 브라우저가 서버에서 페이지에 대한 HTML 응답을 받고 화면에 표시하기 전에 여러 단계가 있다.
    웹 브라우저가 HTML 문서를 읽고, 스타일 입히고 뷰포트에 표시하 는 과정(CRP 과정)

1. (HTML, CSS) + javascript
-> 문서를 읽어들여서 그것들을 파싱하 고 어떤 내용을 페이지에 렌더링할 지 결정.

2. Render Tree
->브라우저가 DOM과 CSSOM을 결합하는 곳이며, 이 프로 세스는 화면에 보이는 모든 콘텐츠의 콘텐츠와 스타일 정보를 모두 포함하 는 최종 렌더링 트리를 출력. 즉 화면에 표시되는 모든 노드의 콘텐 츠 및 스타일 정보를 포함.

3. Layout
-> 브라우저가 페 이지에 표시되는 각 요소 의 크기와 위치를 계산하는 단계

4. Paint
-> 페인트 단계에 도달하면 브라우저는 레이아웃 결 과를 선택하고 픽셀을 화 면에 페인트해야함.

😙 잠깐)
RTL을 처음 접할 때면 RTL을 Jest의 대안으로 혼동하는 경우가 있다. 두 도구는 React 내에서 테스트를 진행할 때 같이 사용되기에 상호 보완 관계라고 볼 수 있다. (엄밀히 말하자면, RTL이 Jest를 포함하는 구조.) 전반적으로 Jest를 통해 기능 테스트를 진행할 수는 있지만, React의 컴포넌트를 렌더링하고 테스트하기 위해서는 몇가지 기능이 더 필요하기 때문.

  • Jest - 자체적인 test runner와 test util 제공
  • RTL - Jest + React 컴포넌트 test util 제공
    그렇기에 리액트 내부의 테스트 코드 작성 방식은 Jest의 방식과 상당 부분 유사.

출처: https://tecoble.techcourse.co.kr/post/2021-10-22-react-testing-library/

1️⃣ Install & Setting

1. CRA 생성

$ yarn create rtl-tutorial
npx create-react-app rtl-tutorial

2. 라이브러리 설치

npm install --save-dev @testing-library/react
npm install --save-dev @testing-library/jest-dom

3. EsLint 설정

  • 개발자들이 특정한 규칙을 가지고 코드를 깔끔하게 짤 수있게 도와주는 라이브러리
  • 자바스크립트를 쓰는 가이드 라인 제시, 문법에 오류가 나면 알려주는 역할
  • ESLint의 주요 기능은 문법 오류 잡는 것이고, 포맷터(formatter) 역할 또한 한다.

<1.> Create React App 으로 리액트를 설치할 때 기본 eslint가 설정

  • 하지만 이 상태로는 VS Code에서 바 로 에러 확인 할 수 없고 앱을 시작했을 때 터미널상에서 볼 수 있다.

<2.> eslint 설정 파일 생성

  • package.json에 eslintConfig 부분 지우고 .eslintrc.json 파일 생성. (이렇게 하면 PROBELMS 탭에서 ESLint에서 주는 기본적인 경고들을 확인 가능.)

<3.> ESLint Testing Plugins 설치

  • testing-library: render로 Dom 그리는 부분
  • jest-dom: expect-matcher로 테스트
 npm install eslint-plugin-testing-library eslint-plugin-jest-dom

잠깐) Plugins?

  • eslint에서 기본으로 제공하지 않는 다양한 규칙을 플러 그인을 통해 사용하기 가능.
    ex) react에 관련된 린트설정을 위해서는 eslint- plugin-react를 사용하면 되며, react hooks에 관련된 규칙을 적용시켜주려면 eslint-plugin-react-hooks를 사용하면 됩니다.

<4.> 내부 설정해주기

  • plugins 항목
    : 플러그인 추가할 때, eslint-plugin-부분 생략가능
  • extends 항목
    : 플러그인을 추가 한 후에 규칙을 정해줘야 사용가능. 그래서 extends 항목에 사용하고자 하는 규칙을 설정. vue, angular, react 중에 react를 위한 규칙 recommended는 추천이 되는 걸 사용. (만약 규칙을 변경하고자 할 때는 rule 항목 추가)

<5.> lint 작동확인

4. Prettier 설정

  • 주로 코드 형식을 맞추는데 사용
  • 작은따옴표(')를 사용할지 큰 따옴표(")를 사용할지, Indent 값을 2로 줄지 4로 줄지 등, 에러 찾는것이 아닌 코드

설치

  • npm 으로 설치
    : 여러 개발자와 같은 포맷 유지에 더 좋음
  • VSCODE 익스텐션으로도 가능
    : 혼자서 편하게 설치해서 사용하기 좋음

2️⃣ Test Code

1. Profile.js & App.js

<Profile.js>
import React from 'react';

const Profile = ({ username, name }) => {
  return (
    <div>
      <b>{username}</b>&nbsp;
      <span>({name})</span>
    </div>
  );
};

export default Profile;
<App.js>
import React from 'react';
import Profile from './Profile';

const App = () => {
  return <Profile username="velopert" name="김민준" />;
};

export default App;

2. Profile.test.js
- 컴포넌트를 렌더링할 때는 render()함수를 사용
-> 이 함수가 호출되면 그 결과물에는 DOM을 선택할 수 있는 다양한 쿼리들과 container가 포함(container는 해당 컴포넌트의 최상위 DOM을 가리킨다. 이를 가지고 스냅샷 테스팅 가능)

import React from 'react';
import { render } from 'react-testing-library';
import Profile from './Profile';

describe('<Profile />', () => {
  it('matches snapshot', () => {
  	// Profile컴포넌트 렌더링하는 utils변수 생성
    const utils = render(<Profile username="houya" name="박정호" />);
    expect(utils.container).toMatchSnapshot(); //최상위 DOM을 기준으로 스냅샷 생성
  });
  it('shows the props correctly', () => {
    const utils = render(<Profile username="houya" name="박정호" />);
    utils.getByText('houya'); // houya 라는 텍스트를 가진 엘리먼트가 있는지 확인
    utils.getByText('(박정호)'); // (박정호) 이라는 텍스트를 가진 엘리먼트가 있는지 확인
    utils.getByText(/박/); // 정규식 /박/ 을 통과하는 엘리먼트가 있는지 확인
  });
});

3. 스냅샷 테스팅 생성

  • 렌더링된 결과가 이전의 렌더링된 결과와 일치하는지 확인
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<Profile /> matches snapshot 1`] = `
<div>
  <div>
    <b>
      	houya
    </b>
     
    <span>
      (
      박정호
      )
    </span>
  </div>
</div>
`;

3️⃣ 다양한 쿼리

  • render함수 실행 후 그 결과물 안에 다양한 쿼리함수들 존재
  • testing-library/react의 기반인 dom-testing-library에서 지원하는 함수들
  • 이 쿼리 함수들은 Variant와 Queries의 조합으로 네이밍이 이루어진다.

Variant

getBy

  • getBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나를 선택. 만약에 없으면 에러가 발생.

getAllBy

  • getAllBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개를 선택. 만약에 하나도 없으면 에러가 발생.

queryBy

  • queryBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나를 선택. 만약에 존재하지 않아도 에러가 발생.

queryAllBy

  • queryAllBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개를 선택. 만약에 존재하지 않아도 에러가 발생.

findBy

  • findBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나가 나타날 때 까지 기다렸다가 해당 DOM 을 선택하는 Promise 를 반환. 기본 timeout 인 4500ms 이후에도 나타나지 않으면 에러가 발생.

findAllBy

  • findBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개가 나타날 때 까지 기다렸다가 해당 DOM 을 선택하는 Promise 를 반환. 기본 timeout 인 4500ms 이후에도 나타나지 않으면 에러가 발생.

Queries

ByLabelText

  • ByLabelText 는 label 이 있는 input 의 label 내용으로 input 을 선택.
<label for="username-input">아이디</label>
<input id="username-input" />

const inputNode = getByLabelText('아이디');

ByPlaceholderText

  • ByPlaceholderText 는 placeholder 값으로 input 및 textarea 를 선택.
<input placeholder="아이디" />;

const inputNode = getByPlaceholderText('아이디');

ByText

  • ByText는 엘리먼트가 가지고 있는 텍스트 값으로 DOM 을 선택.
<div>Hello World!</div>;

const div = getByText('Hello World!');
  • 참고로, 텍스트 값에 정규식을 넣어도 작동.

const div = getByText(/^Hello/);

ByAltText

  • ByAltText 는 alt 속성을 가지고 있는 엘리먼트 (주로 img) 를 선택.
<img src="/awesome.png" alt="awesome image" />;

const imgAwesome = getByAltText('awesomse image');

ByTitle

  • ByTitle 은 title 속성을 가지고 있는 DOM 혹은 title 엘리먼트를 지니고있는 SVG 를 선택 할 때 사용.

  • title 속성은 html 에서 툴팁을 보여줘야 하는 상황에 사용하곤 함.

<p>
  <span title="React">리액트</span>는 짱 멋진 라이브러리다.
</p>

<svg>
  <title>Delete</title>
  <g><path/></g>
</svg>

const spanReact = getByTitle('React');
const svgDelete = getByTitle('Delete');

ByDisplayValue

  • ByDisplayValue 는 input, textarea, select 가 지니고 있는 현재 값을 가지고 엘리먼트를 선택.
<input value="text" />;

const input = getByDisplayValue('text');

ByRole

  • ByRole은 특정 role 값을 지니고 있는 엘리먼트를 선택.
<span role="button">삭제</span>;

const spanRemove = getByRole('button');

ByTestId

  • ByTestId 는 다른 방법으로 못 선택할때 사용하는 방법, 특정 DOM 에 직접 test 할 때 사용할 id 를 달아서 선택하는 것을 의미.
<div data-testid="commondiv">흔한 div</div>;

const commonDiv = getByTestId('commondiv');

주의!

  • camelCase 형식으로 작성X.
  • 값을 설정할때 data-testid="..." 와 같이 작성
  • ByTestId 는 다른 방법으로 선택할 수 없을때에만 사용.

쿼리 우선순위

1. getByLabelText
2. getByPlaceholderText
3. getByText
4. getByDisplayValue
5. getByAltText
6. getByTitle
7. getByRole
8. getByTestId

useEvent > fireEvent

  • userEvent는 fireEvent 를 사용해서 만들어졌다.
  • userEvent의 내 부 코드를 보면 fireEvent를 사용하면서 엘리먼트의 타입에 따라서 Label을 클릭했을 때, checkbox, radio 을 클릭했을 때 그 엘리먼트 타 입에 맞는 더욱 적절한 반응을 보여준다.

ex) fireEvent 로 버튼을 클릭하면 fireEvent.click(button) 버 튼이 focus X.
But, userEvent로 클릭하면 userEvent.click(button) 버튼이 focus O.

이렇게 실제 사용하는 유저가 보기에 실제 버튼을 클릭하는 행위가 더 잘 표현되기에 userEvent를 사용하는 게 더 추천되는 방법.

참고 문서 : https://testing- library.com/docs/queries/about/#priority

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글