React Testing

Seulyi Yoo·2022년 7월 12일
0

React

목록 보기
11/12
post-thumbnail

JavaScript Unit Test

Unit Test

  • 통합테스트에 비해 빠르고 쉬움
  • 통합테스트를 진행하기 전에 문제를 찾아낼 수 있음(통합테스트 성공보장 X)
  • 테스트 코드가 동작을 설명하는 명세가 됨(테스트를 읽고 어떤 동작을 하는지 예측 가능)
  • 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증할 수 있음
  • 리팩토링 시에 안정성을 확보할 수 있음
  • 개발 및 테스팅에 대한 시간과 비용을 절약할 수 있음
  • TDD(Test-driven Development)

Jest 사용

Facebook/jest

  • React 의 영향이 크겠지만 가장 HOT 한 도구
  • Easy Setup
  • Instant Feedback
    • 고친 파일만 빠르게 테스트 다시 해주는 기능 등..
  • Snapshot Testing
    • Component Test 에 중요한 역할을 하는 Snapshot

npm i jest -D

npm init -y

"scripts" : {
	"test" : "jest"
}
// example.test.js

test('adds 1 + 2 th equal 3', () => {
	expect(1 + 2).toBe(3);
});

npm test

// example.test.js

describe("expect test", () => {
  it('37 equal 37', () => {
    expect(37).toBe(3);
  });
});

// example.test.js

describe("expect test", () => {
  it('37 equal 37', () => {
    expect(37).toBe(37);
  });
  it('{age: 39} to equal {age: 39}', () => {
    expect({age: 39}).toEqual({age: 39});
  });
});

npx jest —watchAll
(항상 테스트가 켜져있는 상태)

// example.test.js

describe("expect test", () => {
  it('.toHaveLength', () => {
    expect("Hello").toHaveLength(5); // 텍스트의 길이
  });
});
// example.test.js

describe("expect test", () => {
  
	it('.toHaveProperty', () => {
    expect({name: "Mark"}).toHaveProperty('name');
    expect({name: "Mark"}).toHaveProperty('name', "Mark");
  }); // property를 가지고 있는지 
  
	it('.toBeDefined', () => {
    expect({name: "Mark"}.name).toBeDefined();
  }); // 정의 되어 있는지 
  
	it('.toBeFalsy', () => {
    expect(false).toBeFalsy();
    expect(0).toBeFalsy();
    expect("").toBeFalsy();
    expect(null).toBeFalsy();
    expect(undefined).toBeFalsy();
    expect(NaN).toBeFalsy();
  }); // falsy 한 값인지 

  it('.toBeGreaterThan', () => {
    expect(10).toBeGreaterThan(9);
  }); // 값이 큰지 

  it('.toBeGreaterThanOrEqual', () => {
    expect(10).toBeGreaterThanOrEqual(10);
  }); // 값이 크거나 같은지 

  it('.toBeInstanceOf', () => {
    class Foo {}
    expect(new Foo()).toBeInstanceOf(Foo);
  }); // instance 인지?

});
// .not.to~

describe('not.to~ test', () => {

  it('.not.toBe', () => {
    expect(37).not.toBe(36);
  });

  it('.not.toBeFalsy', () => {
    expect(true).not.toBeFalsy();
    expect(1).not.toBeFalsy();
    expect('hello').not.toBeFalsy();
    expect({}).not.toBeFalsy();
  });

  it('.not.toBeGreaterThan', () => {
    expect(10).not.toBeGreaterThan(10);
  });

});
// async test with done callback
// 비동기 방식
// 예전 방식

describe('use async test', () => {

  it('setTimeout without done', () => {
    setTimeout(()=>{
      expect(37).toBe(36);
    }, 1000);
  });

  it('setTimeout without done', done => {
    setTimeout(()=>{
      expect(37).toBe(36);
      done();
    }, 1000);
  });

});
// async test with done promise

describe('use async test', () => {

  it('promise then', () => {
    function p(){
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(37);
        }, 1000);
      });
    }
    return p().then(data => expect(data).toBe(37));
  });

  it('promise catch', () => {
    function p(){
      return new Promise((resolve, rejects) => {
        setTimeout(() => {
          rejects(new Error('error'));
        }, 1000);
      });
    }
    return p().catch(e => expect(e).toBeInstanceOf(Error));
  });

});

-----------------------------------------

describe('use async test', () => {

  it('promise .resolves', () => {
    function p(){
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(37);
        }, 1000);
      });
    }
    return expect(p()).resolves.toBe(37);
  });

  it('promise .rejects', () => {
    function p(){
      return new Promise((resolve, rejects) => {
        setTimeout(() => {
          rejects(new Error('error'));
        }, 1000);
      });
    }
    return expect(p()).rejects.toBeInstanceOf(Error);
  });

});
// async test with async-await
// 가장 좋은 방식

describe('use async test', () => {

  it('async-await', async() => {
    function p(){
      return new Promise(resolve => {
        setTimeout(() => {
          resolve(37);
        }, 1000);
      });
    }
    const data = await p();
    return expect(data).toBe(37);
  });

});

-------------------------------------------

describe('use async test', () => {

  it('async-await, catch', async() => {
    function p(){
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          reject(new Error('error'));
        }, 1000);
      });
    }
    try{
      await p();
    } catch(error) {
      expect(error).toBeInstanceOf(Error);
    }
  });

});

React Component Test

npx create-react-app react-component-test

npm test

a


Testing-library / react 활용

Button 컴포넌트

  • 컴포넌트 생성
  • ‘button’ 이라고 쓰여있는 엘리먼트는 HTMLButtonElement 이다.
  • 버튼을 클릭하면, p태그 안에 ‘버튼이 방금 눌렸다.” 라고 쓰여진다.
  • 버튼을 클릭하기 전에는, p태그 안에 ‘버튼이 눌리지 않았다.” 라고 쓰여진다.
  • 버튼을 클릭하고 5초 뒤에는, p태그 안에 ‘버튼이 눌리지 않았다.” 라고 쓰여진다.
  • 버튼을 클릭하면, 5초동안 버튼이 비활성화 된다.
// src/components/Button.test.js

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

describe('Button 컴포넌트 (@testing-library/react)', () => {

  it('컴포넌트가 정상적으로 생성된다.', () => {
    const button = render(<Button />);
    expect(button).not.toBe(null);
  });
  
  it('"button" 이라고 쓰여있는 엘리먼트는 HTMLButtonElement 이다.', () => {
    const {getByText} = render(<Button />);
    const buttonElement = getByText('button');
    expect(buttonElement).toBeInstanceOf(HTMLButtonElement);
  });
  
  it('버튼을 클릭하면, p 태그 안에 "버튼이 방금 눌렸다." 라고 쓰여진다.', () => {
    const {getByText} = render(<Button />);
    const buttonElement = getByText('button');
    fireEvent.click(buttonElement);
    const p = getByText("버튼이 방금 눌렸다.");
    expect(p).not.toBeNull();
    expect(p).toBeInstanceOf(HTMLParagraphElement);
  });

  it('버튼을 클릭하기 전에는, p태그 안에 "버튼이 눌리지 않았다.” 라고 쓰여진다.', () => {
    const {getByText} = render(<Button />);
    const p = getByText("버튼이 눌리지 않았다.");
    expect(p).not.toBeNull();
    expect(p).toBeInstanceOf(HTMLParagraphElement);
  });

  it('버튼을 클릭하고 5초 뒤에는, p태그 안에 "버튼이 눌리지 않았다.” 라고 쓰여진다.', () => {
    jest.useFakeTimers();

    const {getByText} = render(<Button />);
    const buttonElement = getByText('button');
    fireEvent.click(buttonElement);

    // 5초 흐른다.
    act(()=>{
      jest.advanceTimersByTime(5000);
    });

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

  it('버튼을 클릭하면, 5초동안 버튼이 비활성화 된다.', () => {
    jest.useFakeTimers();

    const {getByText} = render(<Button />);
    const buttonElement = getByText('button');
    fireEvent.click(buttonElement);

    // 비활성화
    expect(buttonElement).toBeDisabled();

    // 5초 흐른다.
    act(()=>{
      jest.advanceTimersByTime(5000);
    });

    // 활성화
    expect(buttonElement).toBeDisabled();
  });

});
// src/components/Button.jsx

import { useRef, useState, useEffect } from "react";

const BUTTON_TEXT = {
  NORMAL: "버튼이 눌리지 않았다.",
  CLICKED: "버튼이 방금 눌렸다."
};

export default function Button (){

  const [message, setMessage] = useState(BUTTON_TEXT.NORMAL);

  const timer = useRef();

  useEffect(() => { 
    return () => {
      if(timer.current) {
        clearTimeout(timer.current);
      }
    };
  }, []);

  return (
    <div>
      <button onClick={click} disabled={message === BUTTON_TEXT.CLICKED}>button</button>
      <p>{message}</p>
    </div>
  );

  function click () {
    setMessage(BUTTON_TEXT.CLICKED);
    timer.current = setTimeout(()=>{
      setMessage(BUTTON_TEXT.NORMAL);
    }, 5000);
  }

}
profile
성장하는 개발자 유슬이 입니다!

0개의 댓글