본문 바로가기
공부/React

2022.08.15 - Unit Test, JEST, TEST 코드작성

by 기묜몬 2022. 8. 15.

- JavaScript Unit Test

: 통합테스트에 비해 빠르고 쉽다.

통합테스트를 진행하기 전에 문제를 찾아 낼 수 있다. (단, 통합테스트 성공 보장은 없음)

테스트 코드가 살아있는(동작을 설명하는) 명세가 됨테스트를 읽고 어떻게 동작하는지도 예측 가능함

선 코딩 후, 몰아서 단위테스트가 아니라 TDD를 해야함단위테스트를 작성하고, 그것에 맞는 코딩을 작성하는 버릇이 필요

 

- Jest
Javascript testing FrameWork로 CRA에서 기본적으로 제공하는 test 로서 입지를 다져 대중적으로 쓰이고 있음
facebook의 openSource로 가장 핫한 테스트 도구
Easy Setup, Instant Feedback(고친 파일만 빠르게 테스트 다시 해주는 기능 등)
Snapshot Testing (컴포넌트 테스트에 중요한 역할을 하는 스냅샷)
CRA로 project를 만들면 jest가 이미 설정되어 있어 설치할 필요 없음 

 

설치 :  npm i jest -D

package.json script에 test 변경하기 : "test":"jest"

 

 

1. test code 비교함수

to.~~();

//test는 it으로도 쓸 수 있고, 테스트코드를 분야별로 묶을땐 describe라는 함수를 사용함
describe("expect test", () => {
  it("adds 37 to equal 37", () => {
    expect(37).toBe(37);
    // 1+2의 결과가 3이어야함
    // toBe() -> reference까지 고려한 비교
  });
  it("{age:29} to equal {age:29}", () => {
    expect({ age: 29 }).toEqual({ age: 29 });
    // toEqual() -> reference 위치 고려하지 않고 생긴것만 고려
  });
  it(".toHaveLength", () => {
    expect("hello").toHaveLength(5);
    // toHaveLength() -> 문자열의 길이 test
  });
  it(".toHaveProperty", () => {
    expect({ name: "mark" }).toHaveProperty("name");
    expect({ name: "mark" }).toHaveProperty("name", "mark");
    //  toHaveProperty() -> property와 value까지 test
  });
  it(".toBeDefined", () => {
    expect({ name: "mark" }.name).toBeDefined();
    //  toBeDefined() -> undefined가 아닌지 확인
  });
  it(".toBeFalsy", () => {
    expect(false).toBeFalsy();
    expect(0).toBeFalsy();
    expect("").toBeFalsy();
    expect(null).toBeFalsy();
    expect(undefined).toBeFalsy();
    expect(NaN).toBeFalsy();
    //  toBeFalsy() -> falsy한 값인지 확인
  });
  it(".toBeGreaterThan", () => {
    expect(10).toBeGreaterThan(9);
    //toBeGreaterThan() -> 기대값이 더 큰지 비교 확인
  });
  it(".toBeGreaterThanOrEqual", () => {
    // 기대값이 더 크거나 같은지 test
    expect(10).toBeGreaterThanOrEqual(10);
  });
  it(".toBeInstanceOf", () => {
    // 기대값인 instance가 해당 클래스에 속하는지 test
    class Foo {}
    expect(new Foo()).toBeInstanceOf(Foo);
  });
  it(".toBeNull", () => {
    // 기대값이 null인지 test
    expect(null).toBeNull();
  });

  it(".toBeTruthy", () => {
    // 기대값이 Truthy 한지
    expect(true).toBeTruthy();
    expect(1).toBeTruthy();
    expect("hello").toBeTruthy();
    expect({}).toBeTruthy();
  });

  it(".toBeUndefined", () => {
    // 기대값이 Undefined 한지
    expect(NaN).toBeNaN();
  });
});

/*
비동기 관련해서 test하는 방식은 3가지가 있음
Callback, Promise, await-async
*/

 

 

2. test 예시를 통한 코드 작성 

2-1. test 코드

  - AAA (Arrange Act Assert, test 방법론 중의 하나 임) 
    state가 변화가 있는 test를 하는 경우 act()라는 함수를 사용해서 해당 함수를 넣고 state가 변화하는 행위가 지나가도록 해야함
    여기서 시간이 흐르는 것도 state가 변하기 때문에 act를 사용함

//Button.test.js
import { act, fireEvent, getByText, render } from "@testing-library/react";
import Button from "./Button";

// (1)
describe("Button 컴포넌트 (@testing-library/react)", () => {
  it("컴포넌트가 정상적으로 생성된다.", () => {
    const button = render(<Button />);
    expect(button).not.toBe(null);
  });
// (2)
  it('"button"이라고 쓰여있는 엘리먼트는 HTMLButtonElement이다.', () => {
    const { getByText } = render(<Button />);
    const buttonElement = getByText("button");
    // Button컴포넌트를 랜더해서 그 안에 getByText로, text로 검색해서 버튼이라는 화면의 텍스트를 가져오는 엘리먼트를 찾아오는 함수를 이용하고,
    // 그 함수의 결과물이 buttonElement일 것이다.라는 test코드임
    expect(buttonElement).toBeInstanceOf(HTMLButtonElement);
  });
// (3)
  it('버튼을 클릭하면, p 태그 안에 "버튼이 방금 눌렸다." 라고 쓰여진다.', () => {
    const { getByText } = render(<Button />);
    const buttonElement = getByText("button");
    // fireEvent: test를 도와주는 객체
    fireEvent.click(buttonElement);
    const p = getByText("버튼이 방금 눌렸다.");
    expect(p).not.toBeNull();
    expect(p).toBeInstanceOf(HTMLParagraphElement);
  });
// (4)
  it('버튼을 클릭하기 전에는, p 태그 안에 "버튼이 눌리지 않았다."라고 쓰여진다.', () => {
    const { getByText } = render(<Button />);

    const p = getByText("버튼이 눌리지 않았다.");
    expect(p).not.toBeNull();
    expect(p).toBeInstanceOf(HTMLParagraphElement);
  });
// (5)
  it('버튼을 클릭하고 5초 뒤에는, p 태그 안에 "버튼이 눌리지 않았다." 라고 쓰여진다.', () => {
    //jest.useFakeTimers() : 진짜 시간의 경과를 기다리기 보다는, 프로그램의 시간만 돌려서 test한다.
    jest.useFakeTimers();

    const { getByText } = render(<Button />);
    //버튼 랜더
    const buttonElement = getByText("button");
    // 버튼 엘리먼트 구하기
    fireEvent.click(buttonElement);
    // fireEvent: test를 도와주는 객체 , 여기서는 클릭에 해당

    // 이 사이에 5초가 흐른다. FakeTimer
    act(() => {
      jest.advanceTimersByTime(5000);
    });

    const p = getByText("버튼이 눌리지 않았다.");
    expect(p).not.toBeNull();
    expect(p).toBeInstanceOf(HTMLParagraphElement);
  });
});

 

2-2. 실행되는 코드

// Button.jsx
import { useState } from "react";

// 실제코드 작성
export default function Button() {
  const [message, setMessage] = useState("버튼이 눌리지 않았다.");
  return (
    <div>
      <button onClick={click}>button</button>
      <p>{message}</p>
    </div>
  );

  function click() {
    setMessage("버튼이 방금 눌렸다.");
    setTimeout(() => {
      setMessage("버튼이 눌리지 않았다.");
    }, 5000);
  }
}