프론트엔드 테스트 코드, 어디까지 해야 할까?
프론트엔드 개발을 하다 보면 “테스트 코드”라는 말을 자주 듣게 됨.
하지만 실제로 어떤 종류가 있고, 어디까지 해야 하는지는 헷갈릴 때가 많음.
이번엔 단위 테스트, 통합 테스트, E2E 테스트의 차이를 정리해봄.
1. 단위(Unit) 테스트
- 가장 작은 단위의 코드를 테스트함.
- 주로 함수나 로직 단위로 테스트를 작성함.
- UI는 신경 안 쓰고, 데이터 처리나 계산 로직이 제대로 동작하는지만 확인함.
예를 들어 API 응답 데이터를 가공하는 함수가 있다고 가정함.
// utils/parseData.js
export function parseUserData(apiResponse) {
return apiResponse.map(item => ({
id: item.user_id,
name: item.user_name.toUpperCase(),
}));
}
// 이 함수가 제대로 동작하는지 테스트함.
import { parseUserData } from '../utils/parseData';
test('API 응답 데이터를 올바르게 변환해야 함', () => {
const apiResponse = [
{ user_id: 1, user_name: 'john' },
{ user_id: 2, user_name: 'alice' },
];
const result = parseUserData(apiResponse);
expect(result).toEqual([
{ id: 1, name: 'JOHN' },
{ id: 2, name: 'ALICE' },
]);
});
2. 통합(Integration) 테스트
- 여러 컴포넌트나 함수가 함께 동작하는 상황을 테스트함.
- 예를 들어, API 호출 + UI 렌더링이 잘 이어지는지 확인하는 단계임
- 보통 실제 API 대신 mock 데이터를 사용함.
- 서버와의 통신은 흉내만 내는 식임.
- 실제 브라우저 동작(클릭, 라우팅)은 포함되지 않음
// UserList.js
import { useEffect, useState } from 'react';
import axios from 'axios';
export default function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
axios.get('/api/users').then(res => setUsers(res.data));
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 테스트 코드에서는 API를 가짜로 만듦.
import { render, screen, waitFor } from '@testing-library/react';
import axios from 'axios';
import UserList from '../UserList';
jest.mock('axios');
test('유저 리스트를 화면에 렌더링해야 함', async () => {
axios.get.mockResolvedValueOnce({
data: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Alice' },
],
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('Alice')).toBeInTheDocument();
});
});
3. E2E(End-to-End) 테스트
- 사용자가 실제로 앱을 사용하는 것처럼 테스트함.
- 브라우저를 띄워서 버튼 클릭, 폼 입력, 페이지 이동까지 다 테스트함.
- 대표적인 도구는 Cypress나 Playwright임.
- 실제 사용자 입장에서 전체 흐름 테스트
- 실행 속도가 느리고 유지보수 비용이 높음
// cypress/e2e/form.cy.js
describe('회원가입 폼', () => {
it('폼을 채우고 제출하면 성공 메시지가 보여야 함', () => {
cy.visit('/signup');
cy.get('input[name="email"]').type('test@example.com');
cy.get('input[name="password"]').type('123456');
cy.get('button[type="submit"]').click();
cy.contains('회원가입 완료').should('be.visible');
});
4. 정리
- 단위 테스트 : 함수, 훅, 유틸을 사용하여 로직만 검증 주로 Jest를 사용함
- 통합 테스트 : API + 컴포넌트를 사용하여 기능 연결을 검증 React Testing Library를 사용함
- E2E 테스트 : 실제 서비스를 이용하는것처럼 테스트 Cypress, Playwright
5. 결론
- API 데이터 파싱, 계산 → 단위 테스트
- API 결과를 컴포넌트로 보여주기 → 통합 테스트
- 실제 사용자가 클릭하고 제출하는 시나리오 → E2E 테스트
프로젝트 초반엔 단위 테스트와 통합 테스트 위주로,
릴리즈 전엔 E2E 테스트를 통해 전체 플로우를 점검하는 게 일반적임.