현재 과제로 인해 간단한 todolist를 만드는 중에 jest를 사용해보고 있다
먼저 홈페이지엔 간단한 타이틀 문구와 로그인과 회원가입을 하러 갈 수 있게 redirect 시켜주는 버튼을 만드려고 한다
우선은 TDD를 진행하기 위해서 홈페이지에서의 로직을 이전 포스팅처럼 정리해 두었고 와이어 프레임을 보며 머릿속에서 진행되는 흐름을 메모장에 옮겨적으며 testing library에 접목시켰다
일단은
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import Home from "../pages/Home";
describe("<Home />", () => {
// 각 테스트가 시작 전 렌더링 하기
beforeEach(() => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
});
//홈페이지 타이틀 메세지가 잘 뜨는지 확인
test("render home title massage", () => {
const HomeTitleMessage = screen.getByRole("h1", {
name: "TODOLIST에 오신 걸 환영합니다!",
});
expect(HomeTitleMessage).toBeInTheDocument();
});
//Login 버튼 누를시 signin페이지 이동
test("click login button redirect /signin", () => {
const loginButton = screen.getByRole("button", { name: "로그인" });
fireEvent.click(loginButton);
expect(window.location.pathname).toBe("/signin");
});
//signup버튼 누를시 signup페이지 이동
test("click signup button redirect /signup", () => {
const signupButton = screen.getByRole("button", { name: "회원가입" });
fireEvent.click(signupButton);
expect(window.location.pathname).toBe("/signup");
});
});
처럼 작성되었는데 Home 페이지 컴포넌트 테스트 코드로 describe를 사용하여 HOME컴포넌트에 관련된 테스트를 함께 그룹화하는 블록으로 만들었다
이후 작성을 하다보니 각 테스트마다 계속해서 render 함수를 불러오는 부분이 존재했고 이에 같은 코드가 반복된다 느껴 찾아보다
beforeEach
라는 함수를 알게되었다
beforeEach
와 beforeAll
이 있는데 All은 해당 테스트 파일 내 모든 테스트 작업 전에 한번만 실행되는 메소드이고 Each는 각각의 모든 테스트 작업 전에 실행되는 메소드이다
그래서 beforeEach
를 통해 각각의 테스트들 전에 render
를 하도록 시켰고 각 요소는 getByRole
로 찾은 뒤 예상 테스트들을 입력했다
추가적으로 회원가입 페이지와 로그인 페이지 또한 작성했는데
회원가입 페이지
import { render, screen, fireEvent,waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import axios from 'axios'
import Signup from "../pages/Signup";
jest.mock("axios")
const mockedAxios = axios as jest.Mocked<typeof axios>
describe("<Signup/>", () => {
beforeEach(() => {
render(
<MemoryRouter>
<Signup />
</MemoryRouter>
);
});
//화면에 렌더되는지 확인
test("Render title and input", () => {
const title = screen.getByRole("h1", { name: "회원가입" });
expect(title).toBeInTheDocument();
const emailInput = screen.getByTestId("email-input");
const passwordInput = screen.getByTestId("password-input");
expect(emailInput).toBeInTheDocument();
expect(passwordInput).toBeInTheDocument();
});
//email 유효성 검사 동작하는지 확인
test("Invalid email input", () => {
const emailInput = screen.getByTestId("email-input");
fireEvent.change(emailInput, { target: { value: "invalid input value" } });
expect(emailInput).toHaveClass("invalid");
});
// password 유효성 검사 동작 하는지 확인
test("password input validation", () => {
const passwordInput = screen.getByTestId("password-input");
fireEvent.change(passwordInput, { target: { value: "short" } });
expect(passwordInput).toHaveClass("invalid");
});
// 유효성 검사 통과시 버튼 disable 동작하는지 확인
test("enable signup button after validation", () => {
const emailInput = screen.getByTestId("email-input");
const passwordInput = screen.getByTestId("password-input");
const signupButton = screen.getByTestId("signup-button");
fireEvent.change(emailInput, { target: { value: "test@test.com" } });
fireEvent.change(passwordInput, { target: { value: "testpassword" } });
expect(signupButton).not.toBeDisabled();
});
//API 통신 테스트 후 성공시 리다이렉트 동작 확인
test("signupAPI request successful and redirect signin", async()=>{
const emailInput = screen.getByTestId("email-input");
const passwordInput = screen.getByTestId("password-input");
const signupButton = screen.getByTestId("signup-button");
fireEvent.change(emailInput, { target: { value: "test@test.com" } });
fireEvent.change(passwordInput, { target: { value: "testpassword" } });
mockedAxios.post.mockResolvedValueOnce({status:201})
fireEvent.click(signupButton)
await waitFor(()=>{
expect(window.location.pathname).toBe("/signin")
})
})
});
로그인 페이지
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import Signin from "../pages/Signin";
import axios from "axios";
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe("<Signup/>", () => {
beforeEach(() => {
render(
<MemoryRouter>
<Signin />
</MemoryRouter>
);
});
//화면에 렌더되는지 확인
test("Render title and input", () => {
const title = screen.getByRole("h1", { name: "로그인" });
expect(title).toBeInTheDocument();
const emailInput = screen.getByTestId("email-input");
const passwordInput = screen.getByTestId("password-input");
expect(emailInput).toBeInTheDocument();
expect(passwordInput).toBeInTheDocument();
});
//email 유효성 검사 동작하는지 확인
test("Invalid email input", () => {
const emailInput = screen.getByTestId("email-input");
fireEvent.change(emailInput, { target: { value: "invalid input value" } });
expect(emailInput).toHaveClass("invalid");
});
// password 유효성 검사 동작하는지 확인
test("password input validation", () => {
const passwordInput = screen.getByTestId("password-input");
fireEvent.change(passwordInput, { target: { value: "short" } });
expect(passwordInput).toHaveClass("invalid");
});
// 유효성 검사 통과시 버튼 disable 해제 되는지 확인
test("enable login button after validation", () => {
const emailInput = screen.getByRole("input", { name: "email" });
const passwordInput = screen.getByRole("input", { name: "password" });
const signinButton = screen.getByTestId("signin-button");
fireEvent.change(emailInput, { target: { value: "test@test.com" } });
fireEvent.change(passwordInput, { target: { value: "testpassword" } });
expect(signinButton).not.toBeDisabled();
});
//로그인 버튼 누를시 api요청 후 todo로 리다이렉트 되는지 확인
test("signinAPI request successful and redirect todo", async () => {
const emailInput = screen.getByRole("input", { name: "email" });
const passwordInput = screen.getByRole("input", { name: "password" });
const signinButton = screen.getByTestId("signin-button");
fireEvent.change(emailInput, { target: { value: "test@test.com" } });
fireEvent.change(passwordInput, { target: { value: "testpassword" } });
mockedAxios.post.mockResolvedValueOnce({ status: 200 });
fireEvent.click(signinButton);
await waitFor(() => {
expect(window.location.pathname).toBe("/todo");
});
});
});
둘 다 유효성 검사나 input 두개를 받고 버튼을 클릭해 api요청을 보낸다거나 똑같은 부분이 많다
일단 Home 테스트 코드와 달라진 점을 찾자면
1.
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
이런 코드가 생겼다
jest.mock("axios");
해당코드는 api요청이 서버로 직접 가지않게 테스팅 라이브러리가 요청을 가로채는 목적으로 사용되고 const mockedAxios = axios as jest.Mocked<typeof axios>;
는
하단에 작성될mockedAxios.post.mockResolvedValueOnce({ status: 200 });
부분을 작성하기 위해서 타입을 변환 시켰다.
typesciprt에 맞춰 axios를 쓰려하니 작성이 되지 않아 찾아보니 저렇게 as로 타입 단언을 했다
추후에 더 자세하게 알아봐야 할 것 같다
그리고 나머진 비슷하지만 getByTestId
를 통해 요소를 찾고있는 부분을 볼 수 있는데 요소에 data-testid="signin-button"
와 같은 속성을 부여해 요소를 찾도록 했다
이후 fireEvent
의 change
를 통해 value값을 입력하고 위에서 잠깐 나온
mockedAxios.post.mockResolvedValueOnce({status:200})
를 설정해서 해당 요청이 올 시 이러이러한 값(현재 예시에선status:200
)을 건내주고 await waitFor
을 통해 api요청이 끝날 때까지 기다렸다가 리다이렉트 되는 걸 확인하도록 테스트 코드를 작성했다
아직 머릿속에서 구현된 프로그래밍을 적어서 작성했지만 실제 컴포넌트 코드를 적은것은 아니기에 작성된 부분들을 먼저 코드로 구현하고 테스트 해보려 한다.
현재 이 테스트 코드들은 실제 테스트 해보기 전이기에 올바르지 않으나 잘 성공했으면 좋겠다란 마음이 한켠에 든다
이런 유용한 정보를 나눠주셔서 감사합니다.