리액트 컴포넌트 테스트 라이브러리 종류
npm i -D jest // 테스팅 라이브러리로 개발 의존성(-D 옵션)으로 설치
npm i -D @testing-library/react
// react-testing-library가 아닌 꼭 위 패키지 설치!
npm i -D @testing-library/jest-dom
→ 아래 두줄 코드를 테스팅 설정 파일에 추가 (CRA 플젝이면 src/setupTest.js 파일 생성 후 추가)
import "@testing-library/react/cleanup-after-each";
import "@testing-library/jest-dom/extend-expect";
render()
DOM에 컴포넌트를 렌더링
@testing-library/react 모듈로부터 바로 임포트 가능하며, 인자로 렌더링할 React 컴포넌트를 넘김
React Testing Library가 제공하는 모든 쿼리 함수와 기타 유틸 함수를 담고 있는 객체 반환 → 해당 객체로부터 원하는 쿼리 함수만 얻어옴
fireEvent()
특정 이벤트를 발생시켜주는 객체
import { render, fireEvent } from "@testing-library/react";
const { getByText, getByLabelText, getByPlaceholderText } = render(
<YourComponent />
);
내부 상태가 없고 단순히 고정된 텍스트와 이미지로 구성된 NotFound 컴포넌트
import React from "react";
function NotFound({ path }: { path: string }) {
return (
<>
<h2>Page Not Found</h2>
<p>해당 페이지({path})를 찾을 수 없습니다.</p>
<img alt="404" src="https://media.giphy.com/media/14uQ3cOFteDaU/giphy.gif" />
</>
);
}
export default NotFound;
import React from "react";
import { render } from "@testing-library/react";
import NotFound from "./NotFound";
describe("<NotFound />", () => {
it("renders header", () => {
// render 함수의 인자로 테스트할 컴포넌트 전달 -> 리턴 객체로부터 getByText() 함수 얻음
const { getByText } = render(<NotFound path="/abc" />);
// 화면에서 검색할 텍스트인 'Page Not Found'를 함수의 인자로 넘긴 후
// 해당 텍스트를 담고 있는 <h2> 엘리먼트 획득
const header = getByText("Page Not Found");
// jest-dom의 toBeInTheDocument() matcher 함수 - 해당 <h2> 엘리먼트가 화면에 존재하는지 검증
expect(header).toBeInTheDocument();
});
});
import React from "react";
import { render } from "@testing-library/react";
import NotFound from "./NotFound";
describe("<NotFound />", () => {
... 중략
// 특정 요소 내부의 텍스트가 예상과 일치하는지 검증
it("renders paragraph", () => {
// getByText 쿼리 함수는 정규식도 인자로 받을 수 있음
const { getByText } = render(<NotFound path="/abc" />);
const paragraph = getByText(/^해당 페이지/);
// jest-dom의 toHaveTextContent matcher 함수 - <p> 엘리먼트 속 텍스트가 예상과 일치하는지 검증
expect(paragraph).toHaveTextContent("해당 페이지(/abc)를 찾을 수 없습니다");
});
});
import React from "react";
import { render } from "@testing-library/react";
import NotFound from "./NotFound";
describe("<NotFound />", () => {
// 이미지 검증 (내부에 텍스트가 없기에 alt 속성값을 이용)
it("renders image", () => {
const { getByAltText } = render(<NotFound path="/abc" />);
const image = getByAltText("404");
// jest-dom의 toHaveAttribute() matcher 함수 - <img> 요소의 src 속성값이 정확한지 검증
expect(image).toHaveAttribute("src", "https://media.giphy.com/media/14uQ3cOFteDaU/giphy.gif");
});
})
내부 상태에 따라 UI 변화가 생길 수 있는 컴포넌트로 이메일, 비밀번호 입력란 & 버튼으로 구성된 로그인 폼
import React, { useState } from "react";
function LoginForm({ onSubmit }: { onSubmit: (e: React.FormEvent<HTMLFormElement>) => void }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return (<>
<h2>Login</h2>
<form onSubmit={onSubmit}>
<label>
이메일
<input type="email" placeholder="user@test.com" value={email} onChange={({ target: { value } }) => setEmail(value)} />
</label>
<label>
비밀번호
<input type="text" value={password} onChange={({ target: { value } }) => setPassword(value)} />
</label>
<button disabled={!email || !password}>로그인</button>
</form>
</>);
}
export default LoginForm
import { fireEvent, render } from "@testing-library/react";
import React from "react";
import LoginForm from "./LoginForm";
describe("<LoginForm />", () => {
it("enables button when both email and password are entered", () => {
const { getByText, getByLabelText } = render(<LoginForm onSubmit={() => null} />);
const button = getByText("로그인");
const email = getByLabelText("이메일");
const password = getByLabelText("비밀번호");
// 로그인 버튼 비활성화 되어있는지 검증
expect(button).toBeDisabled();
// 입력값 수정
fireEvent.change(email, { target: { value: "user@.test.com" } });
fireEvent.change(password, { target: { value: "1234" } });
// 입력 후 버튼 활성화 되어있는지 검증
expect(button).toBeEnabled();
});
});
jest는 가짜 함수를 생성하며 jest.fn() 함수를 제공
일반 자바스크립트 함수와 동일한 방식으로 인자를 넘겨 호출
const mockFn = jest.fn();
// 호출 결과 undefined
mockFn();
mockFn(1);
mockFn("a");
mockFn([1,2], {a: "b"});
// 리턴값 설정
mockFn.mockReturnValue("I am a mock");
console.log(mockFn()); // I am a mock
// 함수를 통째로 즉석에서 재구현
mockFn.mockImplementation((name) => `I am ${name}`);
console.log(mockFn("test")); // I am test
import { fireEvent, render } from "@testing-library/react";
import React from "react";
import LoginForm from "./LoginForm";
describe("<LoginForm />", () => {
it("submits form when button is clicked", () => {
const obSubmit = jest.fn(e => e.preventDefault());
const { getByText, getByLabelText } = render(<LoginForm onSubmit={obSubmit} />);
const button = getByText("로그인");
const email = getByLabelText("이메일");
const password = getByLabelText("비밀번호");
fireEvent.change(email, { target: { value: "user@test.com" } });
fireEvent.change(password, { target: { value: "1234" } });
fireEvent.click(button);
expect(obSubmit).toHaveBeenCalledTimes(1);
});
})
findBy로 시작하는 함수로 비동기 테스트를 수월하게 할 수 있다.