Udemy의 React - testing 강의를 수강하며 실습한 내용을 정리한 프로젝트입니다.
본 강의에서는 아래 테스트 라이브러리 사용법을 배웁니다.
- Jest
- React Testing Library
yarn install
yarn test-jest # jest 테스트 모두 실행 (./__tests__/...)
yarn test-react # React Testing Library 테스트 모두 실행 (./src/tests/...)
- 테스트를 실행하는 파일의 종류
[name].[test/spec].[js/ts]
__test__/[name].js
test
함수로 테스트를 작성할 수 있다. 첫 번째 매개변수는 테스트의 이름이고, 두 번째는 테스트 함수 부분이다.
test("First", () => {
// assertions를 여기에 작성
expect(2 + 3).toBe(6);
})
5 !== 6이기 때문에 테스트가 실패했다. 성공하려면 toBe(5)
로 바꾸어 주어야 한다.
describe
함수로 여러 테스트를 하나로 묶을 수 있다.test
와it
은 기본적으로 같은 함수이다.
describe("Our First Tests", () => {
test("First", () => {
// assertions 를 여기에 작성
expect(2 + 3).toBe(5);
expect(2 + 3).toBe(5);
expect(2 + 3).toBe(5);
expect(2 + 3).toBe(5);
expect(2 + 3).toBe(5);
})
it("1-Second", () => {
expect(2 + 1).toBe(3);
})
})
describe("Our Second Tests", () => {
it("some other test", () => {
expect(2 + 1).toBe(3);
})
})
describe를 실행하면 이렇게 묶어서 실행된다.
let myValue = 1;
function add(x, y) {
return x + y
}
function fakeAdd(x, y) {
return 3
}
test("First", () => {
let myValue2 = 3;
expect(myValue).toBe(1);
expect(myValue2).toBe(3);
expect(add(myValue, myValue2)).toBe(4);
expect(fakeAdd(1,2)).toBe(3);
// expect(fakeAdd(4, 5)).toBe(9); // => 실패할 것임
})
- expect에 변수나 함수의 평가값을 넣어서 테스트할 수도 있다.
- 함수의 경우에는 가능한 많은 경우의 수를 넣어서 테스트하는 것이 좋다.
let myValue = 1;
beforeAll(() => myValue = 3);
beforeEach(() => {
console.log("Before Test")
myValue = 2
})
afterEach(() => console.log("After the Test"))
test("Before and After", () => {
expect(myValue).toBe(2);
console.log("Inside Second Test")
})
- [before/after]Each ⇒ 모든 테스트 시작 직전에 실행됨
- [before/after]All ⇒ 테스트를 진행하기 한 번만 실행됨
// 이 테스트만 실행됨
test.only("only this one", () => {
expect(add(1, 2)).toBe(3);
})
// 이 테스트만 스킵됨
test.skip("Skipped", () => {
expect(add(1, 2)).toBe(3);
})
// timeout은 기본적으로 5초로 설정되어 있다. 이것보다 오래 걸리면 테스트 실패로 간주한다.
// 세 번째 인자 값을 바꾸어서 timeout 시간을 늘리거나 줄여줄 수 있다.
test.skip("Timeout", () => {
expect(add(1, 2)).toBe(3);
}, 1000)
// 혹은 jest의 설정 자체를 바꾸어 모든 테스트의 timeout 시간을 늘릴 수 있다.
jest.setTimeout(15000);
let numbers = [
[1, 2, 3],
[2,2,4],
[4,5,9]
]
test.each(numbers)("Add %i to %i", (a, b, total) => {
expect(add(a, b)).toBe(total);
})
-
toBe ⇒ 같은 객체를 가리키고 있는가를 확인함.
===
-
toEqual ⇒ 값이 같은지를 확인함.
==
- 따라서, 원시 타입일 때는 관계 없다.
- 그러나 객체 타입일 때는, 같은 메모리 상의 객체가 아니라면 toBe 매칭을 했을 때 오류가 난다.
const obj1 = {name: "hi"} const obj2 = {name: "hi"} test("Matcher", () => { expect(obj1).toBe(obj1); // 성공 expect(obj1).toBe(obj2); // 실패 expect(obj1).toEqual(obj2); // 성공 })
-
그 외에도 다양한 matcher가 있다.
expect(myValue).toBeGreaterThan(0); // > 0
expect(myValue).toBeLessThanOrEqual(10); // <= 10
expect(name).toMatch(/HI/i); // 정규식을 사용하여 검사 가능
expect(animals).toContain("cat");
-
truthy ⇒ 참으로 여겨질 수 있는 모든 값.
-
falsy ⇒ false로 여겨질 수 있는 모든 값.
- 0, undefined, null, NaN, 음수, "", []
let p = null; test("Falsy", () => { expect(p).toBeNull(); expect(p).not.toBeUndefined(); expect(p).toBeFalsy(); expect(p).not.toBeTruthy(); })
- 어떤 행동이 예외를 던질 것을 테스트할 수도 있다. 이 때, 예외의 종류나 메시지를 확인할 수도 있다.
function check() {
throw new Error("Fatal Mistake!");
}
test("Error", () => {
expect(check).toThrow();
expect(check).toThrow(Error);
expect(check).toThrow("Fatal Mistake!");
expect(check).toThrow(/fatal/i);
})
- 비슷한 라이브러리로는 enzyme이 있다.
- Jest가 함수나 변수의 Unit 테스트를 위한 것이었다면, React Testing Library로는 컴포넌트를 테스트할 것이다.
- Jest 테스트를 쓸 때와 동일하게 테스트를 작성해 주면 된다.
react-scripts test
를 실행하고 있으면 파일이 저장되어 변경될 때마다 테스트를 실행한다.- react-testing-library에서 제공하는 render 메소드로 컴포넌트를 그려볼 수 있다.
import React from 'react'
import Hello from '../components/Hello'
import { render } from '@testing-library/react'
test("Component should display Hello text", () => {
const comp = render(<Hello />)
})
comp.debug()
을 하면 DOM Tree를 보여준다.
<body>
<div>
<div>
<h1>
Hello!
</h1>
</div>
</div>
</body>
comp.getByText("Hello!")
로 찾는 텍스트를 포함하고 있는 컴포넌트의 node 정보를 를 가져올 수 있다.
<ref *1> HTMLHeadingElement {
'__reactFiber$2evop6da7ev': <ref *2> FiberNode {
tag: 5,
key: null,
elementType: 'h1',
type: 'h1',
stateNode: [Circular *1],
return: FiberNode {
(이하 생략...)
toBeTruthy()
matcher로 컴포넌트가 존재하는지 확인할 수 있다.
let helloText = comp.getByText("Hello!");
expect(helloText).toBeTruthy();
- render 함수로 얻은 DOM Tree는 deconstruct 가능하다.
- DOM Tree 안에서 얻은 React Node의 값에 접근해서 matcher 함수를 적용해 볼 수 있다.
const { getByText } = render(<Hello />);
let helloText = getByText("Hello!");
expect(helloText).toBeTruthy();
expect(helloText.tagName).toBe("H1");
expect(helloText.textContent).toBe("Hello!");
getBy...
- 찾는 element가 DOM Tree에 없으면 에러를 던진다.queryBy...
- 찾는 element가 없으면 null을 반환findBy...
- Promise를 반환하므로, 비동기로 사용 가능. element가 render될 때까지 기다린다.- element render가 오래 걸릴 경우 사용 가능. 기본 1000ms동안 요소를 찾지 못하면 에러를 던진다.
...AllBy...
- 해당하는 모든 element를 찾는다.- AllBy가 아닌 메소드에서 여러 개의 element를 찾으면 에러를 던진다.
- getAllBy, findAllBy도 해당하는 element가 하나도 없다면 에러를 던진다.
- queryAllBy는 element가 없다면 빈 배열을 반환한다.
getByTestId
- 컴포넌트에data-testid
prop을 주고, 이 값으로 getByTestId를 호출할 수 있다.
- UI 인터랙션을 테스트하기 위해 이벤트를 호출할 수 있다.
fireEvent
let myBtn = getByRole('button');
fireEvent.click(myBtn);
결과
console.log
누름
at onClick (src/components/Hello.js:7:58)
- 'text' 타입의 Input 태그는 input role이 아니라 textbox role을 가진다.
textbox:
Name "":
<input
type="text"
value=""
/>
- fireEvent를 사용해, 이벤트 발생에 따른 상태 변화를 테스트할 수 있다.
let myInput = getByRole('textbox')
expect(myInput).toHaveValue('')
fireEvent.change(myInput, {target: {value: "ooooo"}});
expect(myInput).toHaveValue('ooooo')