Jest | 리액트 컴포넌트 + 스냅샷 테스트

Kate Jung·2022년 4월 12일
0

Front-end Test

목록 보기
6/17
post-thumbnail

🔍 테스트 명령어

npm test 또는 npm test a

📌 리액트 컴포넌트 테스트

1. 테스트 준비 (컴포넌트 제작)

1.1 프로젝트 띄우기

Create React App으로

1.2 간단한 컴포넌트 제작 (Hello)

  1. 컴포넌트 제작 (src/component/Hello.js)

    export default function Hello() {
      return <h1>Hello!</h1>;
    }
  2. 컴포넌트 import (src/App.js)

    • 불필요한 코드 제거
    • Hello 컴포넌트 import
    import "./App.css";
    import Hello from "./component/Hello"; // 👈 Hello 컴포넌트 import
    
    function App() {
      return (
        <div className="App">
          <Hello /> {/* 👈 불필요한 코드 제거, Hello 사용 */}
        </div>
      );
    }
    
    export default App;
  3. 화면

    잘 보임 (Hello)

1.3 user 제작 후, name 따라 조건부 렌더링

  1. user 제작 후, 전달 (src/App.js)

    import "./App.css";
    import Hello from "./component/Hello";
    
    const user = {   // 👈 user 제작
      name: "Mike",
      age: 30,
    };
    
    function App() {
      return (
        <div className="App">
          <Hello user={user} /> {/* 👈 user 전달 */}
        </div>
      );
    }
    
    export default App;
  2. name 따라 조건부 렌더링 (src/component/Hello.js)

    /* 
    user를 받아서 
    - user의 이름이 있으면 -> Hello 뒤에 이름을 보여줌
    - 없으면 Login 버튼을 보여줌 */
    
    export default function Hello({ user }) {
      return user.name ? <h1>Hello! {user.name}</h1> : <button>Login</button>;
    }
  3. 화면

    • 3-1. name 있을 경우 Hello! 이름 잘 보임
    • 3-2. name 없을(주석 처리) 경우 로그인 버튼 나옴

2. 테스트

▪︎ 별도의 설치 불필요

  • Create React App 의 테스트는 Jest를 사용 → 별도 설치 없이 바로 테스트 코드를 작성
  • package.json 에 이미 test 명령어 존재

2.1 임의 테스트

  • 결과

    실패

  • 실패 원인 확인 (./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();
    });
    
    /* 
    learn react link 가 렌더링 되고 있는지 보고 있는데 이미 지웠음. 
    실패하는 것이 맞음. */

2.2 컴포넌트 테스트 및 방법

  • 테스트 파일 (src/component/Hello.test.js)
    • 파일명: 동일한 이름에 test 를 붙임.

      import { render, screen } from "@testing-library/react"; // 👈 @testing-library/react에 있는 render, screen 이용
      import Hello from "./Hello";
      
      const user = {
        name: "Mike",
        age: 30,
      };
      
      test("Hello라는 글자가 포함되어 있는가?", () => {
        render(<Hello user={user} />); {/* 👈 render를 통해서 Hello 컴포넌트를 불러오고 user를 props로 전달 */}
        const helloEl = screen.getByText(/Hello/i); {/* 👈 screen에서 Hello라는 텍스트가 있는지 확인 */}
        expect(helloEl).toBeInTheDocument(); {/* 👈 테스트 실행 (Document안에 방금 지정한 위 텍스트가 있는지 확인) */}
      });
  • 테스트 결과
    • 잘 통과 됨.
    • “Hello”라는 글자가 포함됨.

📌 스냅샷 테스트

성공하는 케이스를 찍어두고 비교

  • 실패하는 경우

    렌더링된 화면과 찍어둔 화면이 다를 경우

  • toMatchSnapshot() 활용

1. 테스트 과정 예시

name이 있는 경우와 없는 경우

1.1 테스트 코드

src/component/Hello.test.js

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

const user = {  // 👈 이름 있는 user
  name: "Mike",
  age: 30,
};

const user2 = {  // 👈 이름 없는 user
  age: 20,
};

// 👇 이렇게 2가지 경우를 만들었고요.
test("snapshot : name 있음", () => { // 👈 이름 있는 경우
  const view = render(<Hello user={user} />);
  expect(view).toMatchSnapshot(); // 👈 toMatchSnapshot()
});
test("snapshot : name 없음", () => { // 👈 이름 없는 경우
  const view = render(<Hello user={user2} />);
  expect(view).toMatchSnapshot(); // 👈 toMatchSnapshot()
});

test("Hello라는 글자가 포함되어 있는가?", () => {
  render(<Hello user={user} />);
  const helloEl = screen.getByText(/Hello/i);
  expect(helloEl).toBeInTheDocument();
});

1.2 테스트 결과

2개의 스냅샷이 작성되었다라는 메세지 존재

1.3 스냅샷 형성됨

src/component/__snapshots__/Hello.test.js.snap

  • 이름이 없을 때 → 로그인 버튼 有

    이름이 있을 때 → Hello! Mike 찍힘

1.4 스냅샷과 달라지면 테스트 실패

  • user의 이름 수정 (src/component/Hello.test.js)

    (...생략)
    
    const user = {
      name: "Tom", // 👈 이름을 Mike에서 Tom으로 수정
      age: 30,
    };
    
    (...생략)
  • 테스트 결과

    실패

    • 이유

      아까는 Mike로 작성되어 있었기 때문
      스냅샷에는 Mike로 되어 있고 지금 전달한 이름은 Tom

2. 테스트 실패 대안

2.1 스냅샷 업데이트

  • 방법

    테스트 후, u 누르기

  • 업데이트 하는 경우 & 주의

    • [업데이트 하는 경우] 예시

      • Hello.js 파일에 문제 無
      • 단지 이제 테스트는 “Mike”말고 “Tom”으로 진행할 경우
    • [주의] 신중하게 결정

      만약 버그가 있어서 실패한 건데 업데이트 해버리면 이후부터는 버그가 있는 상태에서 테스트가 통과됨

  • 업데이트 후

    • 터미널

      하나의 스냅샷이 업데이트가 됐고 모두 성공

    • Hello.test.js.snap

      업데이트 사항 반영됨

      • Mike → Tom

2.2 버그 수정

[예시] user 전달 하지 않을 경우

  1. user 전달 안하도록 수정 (src/component/Hello.test.js)

    (...생략)
    
    const user2 = {
      age: 20,
    };
    
    (...생략)
    
    test("snapshot : name 없음", () => {
      const view = render(<Hello />); // 👈 user 전달 안함
      expect(view).toMatchSnapshot();
    });
    
    (...생략)
    • 통과 되야 함.

      (로그인 버튼이 나와야 되니까. 아까 찍어둔 스냅샷은 로그인 버튼이 노출되는 화면이 있으니까.)

    • 테스트 결과

      • 실패

      • 로그
        - name을 찾을 수 없다.
        - Hello 컴포넌트에서 에러 발생

  2. 버그 수정 (src/component/Hello.js)

    • 테스트 실패 원인

      • user 없음 → user.name 도 못 찾음 → 에러 발생

      • name이 없든 user자체가 없든 로그인 버튼을 보여주는 것이 맞음

        export default function Hello({ user }) {
          return user.name ? <h1>Hello! {user.name}</h1> : <button>Login</button>;
        		     // 👆
        }
    • 버그 수정

      user의 여부도 체크

      // [수정] user도 있고 user의 이름도 있고
      export default function Hello({ user }) {
        return user && user.name ? ( // 👈 user && 추가
          <h1>Hello! {user.name}</h1>
        ) : (
          <button>Login</button>
        );
      }
    • 테스트 결과

      통과

  3. 리팩토링 (src/component/Hello.js)

    • 옵셔널 체이닝

      • user가 없어도 에러를 내지 않고 undefined 반환 → 믿고 사용 가능

      • 좀 더 간단

        export default function Hello({ user }) {
          return user?.name ? <h1>Hello<! {user.name}</h1> : <button>Login</button>;
        				 // 👆 ? 추가
        }
    • 테스트 결과

      마찬가지로 통과

🔹 (비)추천 하는 경우

  • 추천하는 경우

    • 복잡한 디자인이 있을 때

      코드를 일일이 대조하면서 확인하는 작업 대체

    • 색깔이나, 리스트의 갯수나 텍스트들을 비교

      한 줄, 한 줄 테스트 코드를 짜는 것 대체

  • 비추천 하는 경우

    • 불필요한 테스트가 많아질 수도 있을 때

    • 기획에 따라 UI가 계속 바뀌는 경우

      배경 이미지나 아이콘, 이벤트 문구 등이 자주 바뀌는 환경이라면 테스트는 계속 실패하게 될 것이고 매번 업데이트 해주는 것이 작업의 전부가 될 것

🔸 [응용] 시간에 따라 변화하는 컴포넌트 테스트

🔹 결론

시간에 따라서 변할 수 있는 값들은 테스트 전에 Mock Function 를 이용해서 고정된 값으로 바꾸기

1. 컴포넌트 제작 및 적용

컴포넌트 제작 (현재 초단위 표시)

src/component/Timer.js

  • Timer 컴포넌트 제작

    • 현재의 초를 보여줌

    • 새로 고침 할 때 마다 초가 바뀜. (진입 시점의 초를 보여주기 때문)

export default function Timer() {
  const now = Date.now();
  const sec = new Date(now).getSeconds();
  return <p>현재 {sec}초 입니다.</p>;
}

App.js 에서 컴포넌트 import

src/App.js

import "./App.css";
import Timer from "./component/Timer"; // 👈

function App() {
  return (
    <div className="App">
      <Timer /> {/* 👈 */}
    </div>
  );
}

export default App;

2. 테스트

테스트 코드 작성

src/component/Timer.test.js

import { render } from "@testing-library/react";
import Timer from "./Timer";

test("초 표시", () => {
  const view = render(<Timer />);
  expect(view).toMatchSnapshot();
});

테스트 결과

  • 터미널 결과

    첫 테스트기 때문에 스냅샷 파일이 형성됨.

  • src/component/__snapshots__/Timer.test.js.snap

    34초라고 표시 됨

재 테스트

대부분의 확률로 실패

56초라고 나오고 다시 테스트하면 1초라고 나옴.

매번 업데이트(u) 해도 계속 실패할 것이 뻔함.

→ 해결: mock Function 사용

3. 목 함수 활용

작성

src/component/Timer.test.js

  1. 목 함수 제작

    import { render } from "@testing-library/react";
    import Timer from "./Timer";
    
    test("초 표시", () => {
      Date.now = jest.fn(); // 👈 목 함수 제작
      const view = render(<Timer />);
      expect(view).toMatchSnapshot();
    });
  2. 숫자 반환하는 함수로 작성

    (...생략)
    
    test("초 표시", () => {
      Date.now = jest.fn(() => 12345678); // 👈 숫자 하나 반환
      (...생략)
    });

    이제 Date.now123456789으로 고정

테스트 결과 / 업데이트 / 확인

  1. 테스트

    당연히 실패

  2. u를 눌러서 업데이트

  3. 확인

    src/component/__snapshots__/Timer.test.js.snap

    // Jest Snapshot v1, https://goo.gl/fbAQLP
    
    exports[`초 표시 1`] = `
    Object {
      "asFragment": [Function],
      "baseElement": <body>
        <div>
          <p>
            현재 
            45 👈 45초로 바뀜
            초 입니다.
          </p>
        </div>
      </body>,
    
    (...생략)

테스트

계속 통과

  • 통과 이유

    테스트는 언제나 src/component/Timer.test.js12345678 이 숫자로 수행이 되기 때문


참고

  • 코딩앙마 강좌_Jest
profile
복습 목적 블로그 입니다.

0개의 댓글