프론트엔드에서 테스트는 어떻게 진행할까?

2026. 5. 24. 22:06·React

프론트엔드 테스트는 왜 필요할까?

버튼 하나의 조건을 바꿨는데 모달이 열리지 않을 수 있고, API 응답 구조가 조금 바뀌었는데 리스트가 렌더링되지 않을 수 있습니다. 또는 로그인 여부에 따라 보여야 할 페이지가 잘못 노출될 수도 있습니다.

특히 React 프로젝트에서는 컴포넌트가 많아질수록 상태, props, API, 라우팅, 전역 상태가 복잡하게 연결됩니다. 이때 테스트가 없으면 기능을 수정할 때마다 직접 브라우저를 열고 모든 경우를 손으로 확인해야 합니다.

테스트 코드는 이런 과정을 자동화해 줍니다.

예를 들어 다음과 같은 상황을 코드로 검증할 수 있습니다.

사용자가 검색어를 입력한다.
검색 버튼을 클릭한다.
API 응답이 도착한다.
검색 결과 목록이 화면에 표시된다.

이 흐름이 테스트로 작성되어 있다면, 이후 코드를 리팩토링하더라도 검색 기능이 깨졌는지 빠르게 확인할 수 있습니다.


프론트엔드 테스트의 기본 관점

프론트엔드 테스트에서 중요한 기준은 사용자 관점에서 테스트하는 것입니다.

예를 들어 React 컴포넌트 내부에 isOpen이라는 상태가 있다고 해보겠습니다. 모달이 열렸는지 테스트할 때 isOpen 값이 true인지 직접 확인하는 방식은 좋지 않습니다.

대신 사용자가 실제로 보는 화면을 기준으로 확인하는 것이 좋습니다.

expect(screen.getByText('로그인 정보를 입력해주세요')).toBeInTheDocument();

이 방식은 “모달 상태가 true인가?”를 확인하는 것이 아니라, “사용자 화면에 로그인 안내 문구가 보이는가?”를 확인합니다.

이 차이가 중요합니다. 내부 구현은 언제든 바뀔 수 있지만, 사용자에게 보여야 하는 결과는 유지되어야 하기 때문입니다.


프론트엔드 테스트의 종류

프론트엔드 테스트는 보통 다음과 같이 나눌 수 있습니다.

단위 테스트 함수, 유틸 로직 날짜 포맷, 가격 계산, 배열 필터링
컴포넌트 테스트 개별 UI 컴포넌트 Button, Modal, Input, Card
통합 테스트 여러 컴포넌트와 상태의 연결 검색 폼 + API + 결과 리스트
E2E 테스트 실제 브라우저에서 전체 사용자 흐름 로그인 → 상품 검색 → 결제

각 테스트는 역할이 다릅니다. 모든 것을 하나의 방식으로 테스트하려고 하면 오히려 비효율적입니다.


단위 테스트

단위 테스트는 가장 작은 로직을 검증하는 테스트입니다. 보통 UI보다는 순수 함수나 유틸 함수를 테스트할 때 많이 사용합니다.

예를 들어 날짜를 화면에 보여주기 좋은 형식으로 바꾸는 함수가 있다고 해보겠습니다.

export const formatDate = (date: string) => {
  return date.replaceAll('-', '.');
};

이 함수는 다음과 같이 테스트할 수 있습니다.

import { describe, expect, it } from 'vitest';
import { formatDate } from './formatDate';

describe('formatDate', () => {
  it('날짜 문자열의 하이픈을 점으로 변경한다', () => {
    expect(formatDate('2026-05-19')).toBe('2026.05.19');
  });
});

단위 테스트는 실행 속도가 빠르고, 실패했을 때 원인을 찾기 쉽습니다. 그래서 날짜 변환, 숫자 계산, 필터링, 정렬, 유효성 검사 같은 로직에 적합합니다.


컴포넌트 테스트

컴포넌트 테스트는 React 컴포넌트가 props나 사용자 행동에 따라 올바르게 렌더링되는지 확인합니다.

예를 들어 버튼 컴포넌트가 있다고 해보겠습니다.

type ButtonProps = {
  children: React.ReactNode;
  disabled?: boolean;
  onClick?: () => void;
};

export function Button({ children, disabled, onClick }: ButtonProps) {
  return (
    <button disabled={disabled} onClick={onClick}>
      {children}
    </button>
  );
}

이 컴포넌트는 다음과 같이 테스트할 수 있습니다.

import { render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';

describe('Button', () => {
  it('버튼 텍스트를 렌더링한다', () => {
    render(<Button>저장</Button>);

    expect(screen.getByRole('button', { name: '저장' })).toBeInTheDocument();
  });

  it('버튼을 클릭하면 onClick이 호출된다', async () => {
    const user = userEvent.setup();
    const handleClick = vi.fn();

    render(<Button onClick={handleClick}>저장</Button>);

    await user.click(screen.getByRole('button', { name: '저장' }));

    expect(handleClick).toHaveBeenCalled();
  });
});

여기서도 핵심은 button 태그를 직접 찾는 것이 아니라, 사용자가 접근할 수 있는 역할인 button과 이름인 저장을 기준으로 찾는다는 점입니다.

이 방식은 접근성 측면에서도 좋습니다. 테스트하기 쉬운 컴포넌트는 대체로 사용자와 스크린 리더도 접근하기 쉬운 구조를 갖는 경우가 많습니다.


통합 테스트

통합 테스트는 여러 컴포넌트, 상태, API 요청이 함께 동작하는 흐름을 검증합니다.

예를 들어 애니메이션 검색 페이지가 있다고 해보겠습니다. 이 페이지는 검색어 입력 컴포넌트, 검색 버튼, API 요청, 결과 리스트 컴포넌트가 함께 동작합니다.

통합 테스트는 이런 식으로 진행됩니다.

it('검색어를 입력하고 검색하면 결과 목록이 표시된다', async () => {
  const user = userEvent.setup();

  render(<AnimeSearchPage />);

  await user.type(screen.getByPlaceholderText('애니메이션 검색'), 'naruto');
  await user.click(screen.getByRole('button', { name: '검색' }));

  expect(await screen.findByText('Naruto')).toBeInTheDocument();
});

이 테스트는 내부적으로 어떤 state를 사용하는지, API 함수 이름이 무엇인지에 관심을 두지 않습니다.

중요한 것은 사용자가 검색어를 입력하고 검색했을 때 결과가 보이는지입니다.

프론트엔드에서 실제로 가장 가치 있는 테스트는 이런 통합 테스트인 경우가 많습니다. 사용자가 경험하는 흐름을 직접 검증하기 때문입니다.


API가 필요한 테스트는 어떻게 할까?

프론트엔드 테스트에서 API 요청을 실제 서버에 보내는 것은 보통 권장되지 않습니다.

서버 상태나 네트워크 환경에 따라 테스트가 실패할 수 있고, 테스트 데이터가 바뀌면 결과도 흔들릴 수 있기 때문입니다. 그래서 API가 필요한 테스트에서는 보통 Mocking을 사용합니다.

대표적으로 많이 사용하는 도구가 **MSW(Mock Service Worker)**입니다.

MSW는 프론트엔드 코드가 실제 API를 호출하는 것처럼 동작하게 두면서, 테스트 환경에서는 해당 요청을 가로채서 가짜 응답을 반환합니다.

예를 들어 검색 API를 다음과 같이 모킹할 수 있습니다.

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/anime/search', () => {
    return HttpResponse.json({
      results: [
        {
          id: 1,
          title: 'Naruto',
        },
      ],
    });
  }),
];

그러면 테스트에서는 실제 서버가 없어도 API 응답을 받은 것처럼 화면을 검증할 수 있습니다.

it('API 응답으로 받은 검색 결과를 렌더링한다', async () => {
  render(<AnimeSearchPage />);

  expect(await screen.findByText('Naruto')).toBeInTheDocument();
});

MSW를 사용하면 성공 응답뿐 아니라 실패 응답도 테스트할 수 있습니다.

http.get('/api/anime/search', () => {
  return HttpResponse.json(
    { message: '검색 결과를 불러오지 못했습니다.' },
    { status: 500 }
  );
});

이런 식으로 에러 응답을 만들고, 화면에 에러 메시지가 제대로 표시되는지 확인할 수 있습니다.

expect(
  await screen.findByText('검색 결과를 불러오지 못했습니다.')
).toBeInTheDocument();

로딩, 에러, 빈 상태도 테스트해야 한다

프론트엔드 테스트에서 자주 놓치는 부분이 있습니다. 바로 로딩 상태, 에러 상태, 빈 데이터 상태입니다.

실제 서비스에서는 항상 성공 응답만 오지 않습니다. 데이터가 늦게 올 수도 있고, 실패할 수도 있고, 결과가 없을 수도 있습니다.

그래서 다음과 같은 상태를 테스트하는 것이 좋습니다.

데이터를 불러오는 동안 로딩 UI가 보이는가?
API 요청이 실패했을 때 에러 메시지가 보이는가?
검색 결과가 없을 때 빈 상태 UI가 보이는가?
데이터가 정상적으로 도착하면 리스트가 보이는가?

예를 들어 검색 결과가 없을 때는 다음과 같은 UI가 나올 수 있습니다.

expect(await screen.findByText('검색 결과가 없습니다.')).toBeInTheDocument();

이런 테스트는 서비스의 안정성을 높여줍니다. 사용자는 성공 케이스만 경험하지 않기 때문에, 예외 상황까지 테스트하는 것이 중요합니다.


테스트 커버리지는 어떻게 봐야 할까?

테스트 커버리지는 코드 중 어느 정도가 테스트에 의해 실행되었는지를 나타내는 지표입니다.

예를 들어 커버리지가 80%라면 전체 코드 중 80%가 테스트 실행 과정에서 지나갔다는 의미입니다.

하지만 커버리지 숫자가 높다고 해서 좋은 테스트라고 단정할 수는 없습니다.

중요한 것은 핵심 기능이 테스트되고 있는가입니다.

예를 들어 단순히 텍스트만 보여주는 컴포넌트를 많이 테스트해서 커버리지를 높이는 것보다, 로그인, 검색, 작성, 삭제, 권한 분기, API 에러 처리 같은 중요한 기능을 테스트하는 것이 더 가치 있습니다.

프로젝트에서는 보통 다음과 같은 우선순위로 테스트를 작성하는 것이 좋습니다.

우선순위 테스트 대상

높음 로그인, 회원가입, 검색, 작성, 삭제 등 핵심 사용자 흐름
높음 API 성공, 실패, 빈 데이터 상태
높음 폼 유효성 검사
높음 권한에 따른 화면 분기
중간 공통 컴포넌트
중간 유틸 함수
낮음 단순 정적 UI

커버리지는 목표가 아니라 참고 지표로 보는 것이 좋습니다. 테스트의 목적은 숫자를 채우는 것이 아니라, 기능이 깨졌을 때 빠르게 알아차리는 것입니다.


프론트엔드 테스트 도구 조합

React 프로젝트에서는 보통 다음 조합을 많이 사용합니다.

Vitest: 테스트 실행 도구
React Testing Library: React 컴포넌트 테스트 도구
user-event: 사용자 입력과 클릭 시뮬레이션
MSW: API Mocking
Playwright 또는 Cypress: E2E 테스트

Vite 기반 프로젝트라면 Vitest가 잘 어울립니다. 설정이 간단하고 실행 속도가 빠르기 때문입니다.

React Testing Library는 컴포넌트를 실제 사용자 관점에서 테스트하는 데 적합합니다.

MSW는 API 응답을 안정적으로 제어할 수 있게 해주기 때문에, API 연동이 포함된 테스트에서 유용합니다.

Playwright나 Cypress는 실제 브라우저를 띄워서 로그인부터 페이지 이동까지 전체 흐름을 검증할 때 사용합니다.


테스트는 어디까지 작성해야 할까?

모든 코드를 테스트하려고 하면 오히려 부담이 커집니다. 처음부터 완벽한 테스트 환경을 만들기보다는, 깨지면 치명적인 기능부터 테스트하는 것이 좋습니다.

예를 들어 “인생 애니메이션 9선 선택 서비스”라면 다음 기능부터 테스트할 수 있습니다.

애니메이션을 검색할 수 있는가?
검색 결과가 화면에 표시되는가?
작품을 선택할 수 있는가?
최대 9개까지만 선택되는가?
선택한 작품을 삭제할 수 있는가?
선택한 작품의 순서를 변경할 수 있는가?
결과 페이지에 9개 작품이 정상적으로 표시되는가?
API 요청 실패 시 에러 메시지가 표시되는가?

이런 테스트는 서비스의 핵심 흐름을 직접 보호합니다.

반면 단순히 제목만 출력하는 섹션이나 스타일만 담당하는 컴포넌트는 우선순위를 낮게 잡아도 됩니다.


정리

프론트엔드 테스트는 사용자가 실제로 서비스를 이용하는 흐름을 코드로 검증하는 과정입니다.

단위 테스트는 유틸 함수나 작은 로직을 검증하고, 컴포넌트 테스트는 개별 UI가 올바르게 렌더링되는지 확인합니다. 통합 테스트는 여러 컴포넌트와 API 요청이 연결된 사용자 흐름을 검증합니다. 그리고 E2E 테스트는 실제 브라우저 환경에서 서비스 전체 흐름을 확인합니다.

프론트엔드 테스트에서 중요한 것은 내부 구현을 검증하는 것이 아니라, 사용자가 보는 화면과 행동 결과를 검증하는 것입니다.

따라서 좋은 프론트엔드 테스트는 다음 질문에 답할 수 있어야 합니다.

사용자가 이 기능을 정상적으로 사용할 수 있는가?
데이터가 없거나 실패했을 때도 화면이 안정적으로 동작하는가?
리팩토링 이후에도 핵심 기능이 깨지지 않았는가?

결국 테스트는 코드를 더 복잡하게 만드는 작업이 아니라, 프로젝트를 더 안전하게 변경하기 위한 장치입니다. 특히 React 프로젝트처럼 컴포넌트와 상태가 많아지는 구조에서는 테스트가 있을수록 리팩토링과 기능 추가를 더 자신 있게 진행할 수 있습니다.

'React' 카테고리의 다른 글

폴더 구조와 아키텍처에 대해서 알아보자!  (0) 2026.05.17
에러 핸들링 & 안정성 - 프론트엔드 에러 처리  (0) 2026.05.04
Profiler부터 memo까지 제대로 사용해보자!  (0) 2026.04.27
TanStack Query 에 대해서 알아보기!  (0) 2026.04.04
상태 관리 전략 비교 - Context API, Zustand, Redux Toolkit 중 무엇을 선택해야 할까?  (0) 2026.03.23
'React' 카테고리의 다른 글
  • 폴더 구조와 아키텍처에 대해서 알아보자!
  • 에러 핸들링 & 안정성 - 프론트엔드 에러 처리
  • Profiler부터 memo까지 제대로 사용해보자!
  • TanStack Query 에 대해서 알아보기!
수달군
수달군
  • 수달군
    수달 코딩 공장
    수달군
  • 전체
    오늘
    어제
    • 분류 전체보기 (21)
      • React (10)
      • Next.js (7)
      • TypeScript 딥 다이브! (1)
      • 웹 기초 이론 (0)
      • 코딩 테스트 준비 (1)
        • Python 기본 (1)
      • AI 도구들 (0)
      • 프로젝트 회고 (0)
      • 자료구조 (0)
      • 일상 (0)
      • 해외 여행 (0)
      • 국내 여행 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    코딩
    입력받기
    input
    python
    파이썬
    파이썬 초보
    coding
    입력값
    it
    입력
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
수달군
프론트엔드에서 테스트는 어떻게 진행할까?
상단으로

티스토리툴바