프론트엔드 테스트의 기본 개념과, 테스트 대상에 대한 설명입니다.
작은 단위로 작성되는 테스트
컴포넌트가 잘 렌더링 된다.
컴포넌트의 특정 함수를 실행하면 상태가 우리가 원하는 형태로 바뀐다.
리덕스의 액션 생성 함수가 액션 객체를 잘 만들어낸다.
리덕스의 리듀서에 상태와 액션 객체를 넣어서 호출하면 새로운 상태를 잘 만들어준다.
이렇게, 잘게 쪼낸 기능 단위의 테스트를 하면, 전체적으로 잘 작동하는지 확인하기 위해 통합 테스트를 진행합니다.
기능이 전체적으로 잘 작동하는지 확인하기 위해 사용하는 것이 통합테스트
여러 컴포넌트가 렌더링되고 서로 상호 작용을 잘 하고 있다.
DOM 이벤트를 발생 시켰을 때 우리의 UI 에 원하는 변화가 잘 발생한다.
리덕스와 연동된 컨테이너 컴포넌트의 DOM 에 특정 이벤트를 발생시켰을 때, 우리가 원하는 액션이 잘 디스패치 된다.
A. 리덕스 테스트
B. 컴포넌트 렌더링 테스트
C. 비동기 함수 테스트
D. Hooks
리덕스 부분에서 테스트 해야 되는 것들
액션 생성 함수
리듀서
리덕스에 연결된 컴포넌트 : 컨테이너(Container) 컴포넌트
리덕스에 연결되지 않은 컴포넌트 : 프레젠테이셔널(Presentational) 컴포넌트
**리덕스 테스트 예시 코드
import worst10, {initializeData, worst10Thunk} from '../../store/modules/worst10';
import { worst10MockData } from "../../__mocks__/dataMock";
import {api} from "../../api/api";
import store from "../../store/store";
describe("worst10 리듀서 테스트", ()=>{
// api 호출 함수 모킹
beforeEach(async()=>{
api.get=jest.fn().mockResolvedValue({
data: worst10MockData,
})
});
// 1. 초기 상태 테스트
it("초기 상태 확인", ()=>{
expect(worst10(undefined, {type: '@@INIT'})).toEqual({data:[{}], status:""})
})
// 2. 리듀서 테스트
it('초기화 액션 확인',()=>{
let state= worst10(undefined, initializeData(worst10MockData.results));
expect(state.data).toEqual(worst10MockData.results);
})
// 2-A. Thunk 리듀서 테스트
it('비동기 데이터 패칭 Thunk 확인',async()=>{
await store.dispatch(worst10Thunk({
startDate:"2022-02-02",
endDate:"2022-02-02",
workingHour: "all"
}))
let state=store.getState().worst10;
expect(state.data).toEqual(worst10MockData.results);
expect(state.status).toEqual("success");
});
// 3. 컨테이터 컴포넌트 테스트
it("API를 호출해서, 카드 컴포넌트를 렌더링 해야 한다.", async () => {
await render(<CardListContainer></CardListContainer>);
expect(screen.getByText("가동률 Worst 10")).toBeInTheDocument;
expect(screen.getByText("최초 동작 Worst10")).toBeInTheDocument;
});
})
react-testing-library 사용 : 렌더링 결과를 테스트. 실제 화면에 무엇이 보여지는지, 그리고 어떠한 이벤트가 발생했을 때 화면에 원하는 변화가 생겼는지 이런 것을 확인하기에 최적화된 테스트
컴포넌트 렌더링 테스트에서 해야되는 것들
렌더링 테스트
스냅샷 테스트 (사용 x)
요소 확인
이벤트 발생 후 화면 변화 테스트
**컴포넌트 렌더링 테스트 예시 코드
import React from "react";
import { render, screen, cleanup , fireEvent, RenderResult} from '../utils';
import Login from "../../components/login/Login";
import { findByText } from "@testing-library/react";
describe("로그인 페이지 테스트", ()=>{
let container : RenderResult;
// 1. 렌더링 테스트
beforeEach(()=>{
container=render(<Login></Login>);
})
afterEach(cleanup);
// 2. 요소 확인 테스트
it('로그인 페이지 렌더링 테스트' , async()=>{
expect(screen.getByText("GEC 스마트팩토리 모니터링 솔루션")).toBeInTheDocument;
})
// 3. 이벤트 발생 후 화면 변화 테스트
it('로그인 입력 테스트', async()=>{
const idInput :any= container.getByPlaceholderText("아이디");
const passwordInput :any = container.getByPlaceholderText("패스워드");
expect(idInput.value).toBe('');
fireEvent.change(idInput, {target :{value :'admin'}});
expect(idInput.value).toBe('admin');
expect(passwordInput.value).toBe("");
fireEvent.change(passwordInput, { target: { value: "admin123!" } });
expect(passwordInput.value).toBe("admin123!");
})
it('로그인 입력 실패시', async()=>{
const button = screen.getByText("로그인");
fireEvent.click(button);
expect(
screen.getAllByText("가입하지 않은 아이디거나, 잘못된 비밀번호 입니다")
).toBeInTheDocument();
});
})
REST API 를 호출해야 하는 컴포넌트일 경우, 테스트 코드에서 똑같이 요청을 보낼 수 있지만, 일반적으로 서버에 API 를 직접 호출하지 않고 이를 mocking 합니다.
mocking 이란 ?
단위 테스트 작성 할 때, 해당 코드가 의존하는 부분을 가짜 (mock) 으로 대체하는 기법. 실제로 서버에 API 연결해서 테스트를 진행하는 경우 DB나, 서버 와 같은 실제 인프라에 영향을 줄 수 있습니다. 따라서, 프론트엔드에서 할 수 있는 테스트 역할을 분리하기 위해 가짜 객체를 만들어서 테스트하는 모킹 기법을 사용합니다.
REST API 호출 비동기 함수 테스트 예시
import React from "react";
import {cleanup} from "../utils";
import {api} from "../../api/api";
import {Worst10API} from '../../api/api';
import {worst10MockData} from '../../__mocks__/dataMock';
describe("Worst10 페이지 테스트", ()=>{
let spyGet:any;
beforeEach(async()=>{
// 1. api 호출 함수 모킹
api.get = jest.fn().mockResolvedValue({
data: worst10MockData,
});
})
afterEach(cleanup);
it('모킹 API 테스트',async()=>{
// 2. 비동기 함수 호출
const result= await Worst10API.yulkok(
"2022-02-02",
"2022-02-02",
"all"
);
// 3. 호출 REST API 엔드포인트 확인
expect(spyGet).toBeCalledTimes(1);
expect(spyGet).toBeCalledWith(
"api/example/8?beginDateTime=2022-02-02&endDateTime=2022-02-02&timeType=all"
);
})
})