본문 바로가기
개발

프론트 필수 라이브러리 모음 : 10화 Jest, React Testing Library, Cypress - 테스트 자동화🧩✨

by D-Project 2025. 4. 26.

10화. Jest, React Testing Library, Cypress — 테스트는 개발자의 버팀목🧪

안녕하세요 🌟 오늘은 프론트엔드 필수 라이브러리 시리즈의 열 번째 이야기로, 테스트 자동화의 핵심 도구들인 Jest, React Testing Library, Cypress에 대해 알아볼게요! 이 라이브러리들은 2025년 현재 프론트엔드 테스트의 표준으로 자리 잡았답니다! 🧪

테스트 자동화, 왜 중요할까요? 🤔

많은 개발자들이 테스트 작성을 번거롭게 여기고 뒤로 미루는 경향이 있어요. 하지만 테스트 자동화는 단순히 버그를 찾는 것을 넘어 여러 중요한 이점을 제공합니다:

  • 버그 조기 발견: 문제를 사용자보다 먼저 발견하여 신뢰도 향상
  • 리팩토링 자신감: 코드 개선 시 기존 기능이 손상되지 않는지 확인
  • 문서화 효과: 테스트는 코드의 의도와 동작을 설명하는 살아있는 문서
  • 개발 속도 향상: 장기적으로 디버깅 시간 감소 및 생산성 증가
  • 협업 촉진: 팀원들이 서로의 코드를 쉽게 이해하고 수정 가능

테스트 자동화는 단기적으로는 시간이 들지만, 장기적으로는 개발 속도와 코드 품질을 크게 향상시키는 투자랍니다! 이제 프론트엔드 테스트의 세 가지 핵심 도구를 함께 살펴볼게요!

1. Jest: 자바스크립트 테스트의 표준 🧪

Jest는 Facebook에서 개발한 JavaScript 테스트 프레임워크로, 간단한 설정과 강력한 기능으로 프론트엔드 테스트의 사실상 표준이 되었어요.

Jest의 주요 특징:

  1. 올인원 솔루션 🧰
    • 테스트 러너, 단언(assertion) 라이브러리, 모의(mock) 기능 등 통합 제공
    • 추가 설정 없이 바로 사용 가능
  2. 스냅샷 테스팅 📸
    • UI 컴포넌트의 출력을 저장하고 변경 사항 감지
    • 의도하지 않은 UI 변경 방지
  3. 모의(Mock) 시스템 🎭
    • 복잡한 의존성을 쉽게 대체 가능
    • API 호출, 타이머 등 외부 요소 시뮬레이션
  4. 코드 커버리지 📊
    • 테스트가 코드의 얼마나 많은 부분을 검증하는지 측정
    • 테스트 품질 향상에 도움
// Jest 기본 사용 예시
// utils.js
export function sum(a, b) {
  return a + b;
}

export function fetchUser(id) {
  return fetch(`/api/users/${id}`)
    .then(response => response.json());
}

// utils.test.js
import { sum, fetchUser } from './utils';

// 단순한 유닛 테스트
describe('sum 함수', () => {
  test('두 숫자를 더합니다', () => {
    expect(sum(1, 2)).toBe(3);
    expect(sum(-1, 1)).toBe(0);
    expect(sum(0, 0)).toBe(0);
  });
});

// 비동기 테스트
describe('fetchUser 함수', () => {
  test('사용자 데이터를 가져옵니다', async () => {
    // fetch API 모킹
    global.fetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve({ id: 1, name: '홍길동' })
      })
    );

    const user = await fetchUser(1);

    // fetch가 올바른 URL로 호출되었는지 확인
    expect(global.fetch).toHaveBeenCalledWith('/api/users/1');

    // 결과 검증
    expect(user).toEqual({ id: 1, name: '홍길동' });
  });
});

Jest의 유용한 기능들:

1. 테스트 실행 옵션
# 전체 테스트 실행
jest

# 특정 파일의 테스트만 실행
jest path/to/file.test.js

# 변경된 파일만 테스트
jest --watch

# 테스트 이름으로 필터링
jest -t "sum 함수"

# 코드 커버리지 보고서 생성
jest --coverage
2. 다양한 매처(Matcher) 함수
// 기본 매처
expect(value).toBe(exactValue); // 정확한 값 비교 (===)
expect(value).toEqual(obj); // 깊은 객체 비교
expect(value).toBeTruthy(); // true로 평가되는 값
expect(value).toBeFalsy(); // false로 평가되는 값

// 숫자 관련
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThanOrEqual(10);
expect(value).toBeCloseTo(0.3); // 부동 소수점 비교

// 문자열
expect(string).toMatch(/pattern/);

// 배열/객체
expect(array).toContain('item');
expect(object).toHaveProperty('key', value);

// 예외 처리
expect(() => throwError()).toThrow();
expect(() => throwError()).toThrow('특정 에러 메시지');
3. 모의 함수 (Jest Mocks)
// 함수 모킹
const mockFn = jest.fn();
mockFn.mockReturnValue(42); // 항상 42 반환
mockFn.mockImplementation(x => x * 2); // 구현 제공

// 함수 호출 확인
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith(1, 2);
expect(mockFn).toHaveBeenCalledTimes(2);

// 모듈 모킹
jest.mock('./someModule', () => ({
  someFunction: jest.fn().mockReturnValue('mocked value')
}));

// 타이머 모킹
jest.useFakeTimers();
jest.advanceTimersByTime(1000); // 1초 진행
jest.runAllTimers(); // 모든 타이머 실행
4. 스냅샷 테스팅
// UI 컴포넌트 스냅샷
test('Button 컴포넌트가 올바르게 렌더링됩니다', () => {
  const tree = renderer.create(
    <Button text="Click me" />
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

// 객체 스냅샷
test('설정 객체가 예상과 일치합니다', () => {
  const settings = generateSettings();
  expect(settings).toMatchSnapshot();
});

2025년 Jest의 최신 기능:

  • 향상된 병렬 테스트: 더 빠른 테스트 실행
  • TypeScript 지원 개선: 더 나은 타입 추론 및 타입 체크
  • ESM 지원 강화: ES 모듈 시스템 완벽 지원
  • 스마트 테스트 실행: 코드 변경에 영향 받는 테스트만 실행

2. React Testing Library: 사용자 중심 컴포넌트 테스트 👤

React Testing Library(RTL)는 컴포넌트를 구현 세부사항이 아닌 사용자 관점에서 테스트하도록 도와주는 라이브러리예요. "사용자가 컴포넌트를 어떻게 사용할지"에 집중합니다!

React Testing Library의 주요 특징:

  1. 사용자 중심 철학 👥
    • 내부 구현이 아닌 실제 DOM 요소와 사용자 상호작용에 집중
    • 리팩토링에 강한 테스트 작성 가능
  2. 접근성 강조
    • 접근성 속성을 통한 요소 선택 권장
    • 접근성 있는 UI 구축 촉진
  3. 단순한 API 📝
    • 직관적이고 쉽게 배울 수 있는 API 디자인
    • 불필요한 추상화 최소화
  4. Jest와의 원활한 통합 🔄
    • Jest와 함께 사용하도록 설계됨
    • 완벽한 테스트 솔루션 제공
// React Testing Library 기본 사용 예시
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

describe('Counter 컴포넌트', () => {
  test('초기 카운트는 0입니다', () => {
    render(<Counter />);

    // 화면에서 텍스트로 요소 찾기
    const countElement = screen.getByText(/카운트: 0/i);
    expect(countElement).toBeInTheDocument();
  });

  test('버튼 클릭 시 카운트가 증가합니다', async () => {
    render(<Counter />);

    // 버튼 찾기
    const incrementButton = screen.getByRole('button', { name: /증가/i });

    // 사용자 이벤트 시뮬레이션
    const user = userEvent.setup();
    await user.click(incrementButton);

    // 업데이트된 카운트 확인
    expect(screen.getByText(/카운트: 1/i)).toBeInTheDocument();
  });
});

React Testing Library의 핵심 기능:

1. 요소 선택 (Queries)
// 우선순위에 따른 적절한 쿼리 선택하기

// 1. 접근성 속성 (가장 권장)
const button = screen.getByRole('button', { name: '제출' });
const input = screen.getByLabelText('이메일');

// 2. 텍스트 콘텐츠
const heading = screen.getByText('회원가입');

// 3. Form 요소
const emailInput = screen.getByPlaceholderText('이메일 입력');

// 4. 테스트 ID (다른 방법이 없을 때)
const element = screen.getByTestId('custom-element');

// 다양한 변형 쿼리
// - getBy*: 요소를 찾고, 없으면 오류 발생
// - queryBy*: 요소를 찾고, 없으면 null 반환 (존재하지 않음을 확인할 때)
// - findBy*: 비동기적으로 요소를 찾음 (Promise 반환)
// - getAllBy*, queryAllBy*, findAllBy*: 여러 요소 찾기
2. 사용자 상호작용 (UserEvent)
// userEvent를 사용한 사용자 상호작용 (권장)
import userEvent from '@testing-library/user-event';

test('폼 제출 테스트', async () => {
  render(<LoginForm onSubmit={mockSubmit} />);

  const user = userEvent.setup();

  // 입력 필드에 텍스트 입력
  await user.type(screen.getByLabelText('이메일'), 'test@example.com');
  await user.type(screen.getByLabelText('비밀번호'), 'password123');

  // 체크박스 선택
  await user.click(screen.getByLabelText('로그인 상태 유지'));

  // 제출 버튼 클릭
  await user.click(screen.getByRole('button', { name: '로그인' }));

  // 폼 제출 함수가 호출되었는지 확인
  expect(mockSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123',
    rememberMe: true
  });
});
3. 비동기 테스트
// 비동기 작업 테스트
test('데이터 로딩 테스트', async () => {
  // API 응답 모킹
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: jest.fn().mockResolvedValue([
      { id: 1, name: '제품 1' },
      { id: 2, name: '제품 2' }
    ])
  });

  render(<ProductList />);

  // 초기 로딩 상태 확인
  expect(screen.getByText('로딩 중...')).toBeInTheDocument();

  // 데이터 로드 후 상태 확인 (비동기)
  const product1 = await screen.findByText('제품 1');
  const product2 = await screen.findByText('제품 2');

  expect(product1).toBeInTheDocument();
  expect(product2).toBeInTheDocument();
  expect(screen.queryByText('로딩 중...')).not.toBeInTheDocument();
});
4. 커스텀 렌더링
// 컨텍스트와 함께 컴포넌트 렌더링
const customRender = (ui, { providerProps, ...renderOptions } = {}) => {
  return render(
    <ThemeProvider {...providerProps}>{ui}</ThemeProvider>,
    renderOptions
  );
};

test('테마 적용 테스트', () => {
  const providerProps = { theme: 'dark' };
  customRender(<ThemedButton />, { providerProps });

  const button = screen.getByRole('button');
  expect(button).toHaveClass('dark-theme');
});

React Testing Library로 컴포넌트 테스트하기:

// UserProfile 컴포넌트 테스트 예시

// UserProfile.jsx
function UserProfile({ user, onEdit }) {
  return (
    <div className="user-profile">
      <h2>{user.name}</h2>
      <p>이메일: {user.email}</p>
      <p>역할: {user.role}</p>
      <button onClick={() => onEdit(user.id)}>프로필 수정</button>
    </div>
  );
}

// UserProfile.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserProfile from './UserProfile';

describe('UserProfile 컴포넌트', () => {
  const mockUser = {
    id: 1,
    name: '홍길동',
    email: 'hong@example.com',
    role: '관리자'
  };

  const mockEditFn = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  test('사용자 정보가 올바르게 표시됩니다', () => {
    render(<UserProfile user={mockUser} onEdit={mockEditFn} />);

    expect(screen.getByText('홍길동')).toBeInTheDocument();
    expect(screen.getByText(/이메일: hong@example.com/i)).toBeInTheDocument();
    expect(screen.getByText(/역할: 관리자/i)).toBeInTheDocument();
  });

  test('수정 버튼 클릭 시 onEdit 함수가 호출됩니다', async () => {
    render(<UserProfile user={mockUser} onEdit={mockEditFn} />);

    const editButton = screen.getByRole('button', { name: /프로필 수정/i });

    const user = userEvent.setup();
    await user.click(editButton);

    expect(mockEditFn).toHaveBeenCalledTimes(1);
    expect(mockEditFn).toHaveBeenCalledWith(1);
  });
});

2025년 React Testing Library의 최신 기능:

  • React Server Component 지원: 서버 컴포넌트 테스트 기능
  • 향상된 디버깅 도구: 테스트 실패 원인 파악 용이
  • 스마트 스크린샷: UI 변경 사항 자동 감지 및 시각화
  • 성능 테스트 통합: 컴포넌트 성능 변화 모니터링

3. Cypress: E2E 테스트의 차세대 표준 🌐

Cypress는 웹 애플리케이션의 종단 간(End-to-End) 테스트를 위한 현대적인 도구로, 실제 브라우저에서 사용자 흐름을 테스트할 수 있어요!

Cypress의 주요 특징:

  1. 실시간 리로드와 디버깅 🔄
    • 테스트 실행 중 코드 변경 시 자동 재실행
    • 각 단계에서 DOM 스냅샷으로 디버깅 용이
  2. 자동 대기 ⏱️
    • 요소가 준비될 때까지 자동 대기
    • 명시적 지연 없이 안정적인 테스트
  3. 네트워크 모킹과 제어 🌐
    • API 응답 모킹 및 네트워크 요청 관찰
    • 테스트 환경 완벽 제어
  4. 실제 브라우저 환경 🖥️
    • Chrome, Firefox 등 실제 브라우저에서 테스트 실행
    • JavaScript, CSS 동작 그대로 테스트
// Cypress 기본 사용 예시
// cypress/e2e/login.cy.js
describe('로그인 페이지', () => {
  beforeEach(() => {
    // 각 테스트 전 로그인 페이지 방문
    cy.visit('/login');
  });

  it('유효한 자격 증명으로 로그인 성공', () => {
    // 사용자 입력 시뮬레이션
    cy.get('input[name="email"]').type('user@example.com');
    cy.get('input[name="password"]').type('password123');

    // 폼 제출
    cy.get('button[type="submit"]').click();

    // 로그인 성공 확인
    cy.url().should('include', '/dashboard');
    cy.get('h1').should('contain', '대시보드');

    // 로컬 스토리지에 토큰 저장 확인
    cy.window().its('localStorage.token').should('exist');
  });

  it('잘못된 비밀번호로 로그인 실패', () => {
    cy.get('input[name="email"]').type('user@example.com');
    cy.get('input[name="password"]').type('wrong-password');

    cy.get('button[type="submit"]').click();

    // 오류 메시지 확인
    cy.get('.error-message')
      .should('be.visible')
      .and('contain', '이메일 또는 비밀번호가 올바르지 않습니다');

    // URL 변경되지 않음 확인
    cy.url().should('include', '/login');
  });
});

Cypress의 강력한 기능들:

1. 선택자와 동작
// 다양한 요소 선택 방법
cy.get('button') // CSS 선택자
cy.contains('제출') // 텍스트 내용
cy.get('[data-testid=submit-button]') // 테스트 ID

// 요소와 상호작용
cy.get('input').type('hello world') // 입력
cy.get('button').click() // 클릭
cy.get('a').click() // 링크 클릭
cy.get('.dropdown').select('옵션 1') // 드롭다운 선택
cy.get('[type="checkbox"]').check() // 체크박스 선택

// 복잡한 상호작용
cy.get('input').clear().type('새 텍스트') // 입력 필드 지우고 입력
cy.get('div').scrollTo('bottom') // 스크롤
cy.get('.draggable').drag('.drop-target') // 드래그 앤 드롭
2. 단언문 (Assertions)
// 요소 상태 확인
cy.get('button')
  .should('be.visible') // 보이는지
  .and('be.enabled') // 활성화되었는지
  .and('have.text', '제출') // 텍스트 내용
  .and('have.class', 'primary'); // 클래스

// 다양한 단언문
cy.url().should('include', '/dashboard'); // URL 확인
cy.title().should('eq', '대시보드'); // 페이지 제목
cy.window().its('localStorage.token').should('exist'); // 로컬 스토리지

// 요소의 속성과 스타일
cy.get('input').should('have.attr', 'placeholder', '이메일 입력');
cy.get('button').should('have.css', 'background-color', 'rgb(0, 123, 255)');
3. 네트워크 요청 제어
// API 요청 스파이
cy.intercept('GET', '/api/users').as('getUsers');
cy.visit('/users');
cy.wait('@getUsers').its('response.statusCode').should('eq', 200);

// API 응답 모킹
cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: [
    { id: 1, name: '홍길동' },
    { id: 2, name: '김철수' }
  ]
}).as('getUsers');

// 오류 시나리오 테스트
cy.intercept('POST', '/api/login', {
  statusCode: 401,
  body: { error: '인증 실패' }
}).as('loginError');
4. 커스텀 명령어
// 재사용 가능한 로그인 명령어 (cypress/support/commands.js)
Cypress.Commands.add('login', (email, password) => {
  cy.visit('/login');
  cy.get('input[name="email"]').type(email);
  cy.get('input[name="password"]').type(password);
  cy.get('button[type="submit"]').click();
  cy.url().should('include', '/dashboard');
});

// 테스트에서 사용
it('대시보드 테스트', () => {
  cy.login('user@example.com', 'password123');
  // 이제 로그인 상태에서 테스트 진행
});

실전 E2E 테스트 시나리오:

// 사용자 여정(User Journey) 테스트 예시
describe('사용자 쇼핑 여정', () => {
  beforeEach(() => {
    // API 응답 모킹
    cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts');

    // 사용자는 로그인 상태
    cy.login('customer@example.com', 'password123');
  });

  it('상품을 장바구니에 추가하고 결제할 수 있습니다', () => {
    // 1. 상품 목록 페이지 방문
    cy.visit('/products');
    cy.wait('@getProducts');

    // 2. 첫 번째 상품 상세 페이지 이동
    cy.contains('제품 1').click();
    cy.url().should('include', '/products/1');

    // 3. 장바구니에 추가
    cy.get('button[data-testid="add-to-cart"]').click();
    cy.get('.cart-notification').should('be.visible');

    // 4. 장바구니로 이동
    cy.get('[data-testid="cart-icon"]').click();
    cy.url().should('include', '/cart');

    // 5. 장바구니에 상품이 있는지 확인
    cy.contains('제품 1').should('exist');
    cy.get('.quantity-input').should('have.value', '1');

    // 6. 수량 변경
    cy.get('.quantity-increase').click();
    cy.get('.quantity-input').should('have.value', '2');

    // 7. 금액 확인
    cy.get('.item-total').should('contain', '20,000원');
    cy.get('.cart-total').should('contain', '20,000원');

    // 8. 결제 진행
    cy.intercept('POST', '/api/checkout', {
      statusCode: 200,
      body: { orderId: '12345' }
    }).as('checkout');

    cy.get('button[data-testid="checkout-button"]').click();

    // 9. 배송 정보 입력
    cy.get('input[name="address"]').type('서울시 강남구');
    cy.get('input[name="postalCode"]').type('12345');
    cy.get('select[name="paymentMethod"]').select('신용카드');

    // 10. 주문 완료
    cy.get('button[data-testid="place-order"]').click();
    cy.wait('@checkout');

    // 11. 주문 완료 페이지 확인
    cy.url().should('include', '/order-complete');
    cy.contains('주문이 완료되었습니다').should('be.visible');
    cy.contains('주문 번호: 12345').should('be.visible');
  });
});

Cypress Component Testing:

2025년에는 Cypress의 컴포넌트 테스팅 기능이 더욱 강화되었어요. 이를 통해 E2E 테스트뿐 아니라 독립된 컴포넌트도 실제 브라우저 환경에서 테스트할 수 있습니다:

// Cypress 컴포넌트 테스트 예시
// src/components/Button.cy.jsx
import Button from './Button';

describe('Button 컴포넌트', () => {
  it('텍스트를 올바르게 렌더링합니다', () => {
    cy.mount(<Button text="클릭하세요" />);
    cy.get('button').should('have.text', '클릭하세요');
  });

  it('클릭 이벤트를 발생시킵니다', () => {
    const onClickSpy = cy.spy().as('onClickSpy');
    cy.mount(<Button text="클릭하세요" onClick={onClickSpy} />);

    cy.get('button').click();
    cy.get('@onClickSpy').should('have.been.calledOnce');
  });

  it('비활성화 상태를 지원합니다', () => {
    cy.mount(<Button text="클릭하세요" disabled />);

    cy.get('button')
      .should('be.disabled')
      .and('have.class', 'disabled');
  });
});

2025년 Cypress의 최신 기능:

  • 빠른 실행 모드: 병렬 테스트 및 실행 시간 대폭 단축
  • AI 기반 테스트 생성: 앱 사용 패턴 학습 후 자동 테스트 생성
  • 시각적 회귀 테스트: UI 변경 사항 자동 감지
  • 모바일 테스트 지원 강화: 다양한 모바일 디바이스 시뮬레이션

세 가지 테스트 도구의 이상적인 조합 💡

각 테스트 도구는 서로 다른 수준의 테스트에 특화되어 있어요. 이들을 함께 사용하면 포괄적인 테스트 전략을 구축할 수 있답니다!

테스트 피라미드 접근법:

  1. 유닛 테스트 (Jest) 🧪
    • 개별 함수, 유틸리티, 훅 등의 작은 단위 테스트
    • 빠르고 격리된 테스트로 기본 기능 검증
    • 개발 중 자주 실행하여 즉각적인 피드백
  2. 컴포넌트 테스트 (React Testing Library) 🧩
    • UI 컴포넌트의 렌더링과 상호작용 테스트
    • 사용자 행동 중심의 테스트로 실제 사용 시나리오 검증
    • 컴포넌트 단위로 독립적으로 테스트
  3. E2E 테스트 (Cypress) 🌐
    • 전체 애플리케이션 흐름과 통합 테스트
    • 실제 사용자 시나리오와 주요 비즈니스 프로세스 검증
    • 배포 전 최종 안전망 역할

각 도구의 역할과 비중:

        /\      Cypress (E2E)      적은 수, 중요 흐름 위주
       /  \    
      /    \  
     /      \  
    /        \  React Testing Library     컴포넌트 상호작용, 주요 기능
   /          \  
  /            \  
 /              \  
/________________\  Jest  기본 유틸리티, 함수, 로직     다수, 상세 검증

테스트 자동화 최적화를 위한 팁 💪

테스트를 효과적으로 설정하고 유지하기 위한 몇 가지 중요한 팁을 알아볼게요!

1. CI/CD 파이프라인에 통합

# GitHub Actions 예시 (.github/workflows/test.yml)
name: 테스트 실행

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Node.js 설정
      uses: actions/setup-node@v3
      with:
        node-version: 18

    - name: 의존성 설치
      run: npm ci

    - name: 유닛 테스트 실행
      run: npm run test:unit

    - name: E2E 테스트 실행
      uses: cypress-io/github-action@v5
      with:
        browser: chrome
        headless: true

    - name: 코드 커버리지 보고서 업로드
      uses: codecov/codecov-action@v3

2. 테스트 속도 최적화

// jest.config.js
module.exports = {
  // 병렬 테스트 실행으로 속도 향상
  maxWorkers: '50%',

  // 불필요한 파일 무시
  testPathIgnorePatterns: ['/node_modules/', '/cypress/'],

  // 빠른 변환 캐싱
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { cacheDirectory: '.jest-cache' }]
  },

  // 테스트 실행 중 알림 최소화
  verbose: false
};

// cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    // 브라우저 시작 시간 단축
    experimentalFastStart: true,

    // 불필요한 비디오 녹화 비활성화
    video: false,

    // 실패한 테스트만 스크린샷
    screenshotOnRunFailure: true,

    // 테스트 재시도
    retries: {
      runMode: 2,
      openMode: 0
    }
  }
});

3. 테스트 디버깅 개선

// Jest 디버깅
test.only('문제가 있는 테스트', () => {
  // 이 테스트만 실행
});

// 상세 콘솔 출력
console.log(prettyDOM(container)); // Testing Library

// React Testing Library 디버깅
import { screen } from '@testing-library/react';

screen.debug(); // 현재 DOM 출력

// Cypress 디버깅
cy.pause(); // 테스트 일시 중지
cy.debug(); // 개발자 도구 열기

// 요소에 하이라이트 표시
cy.get('.element').then($el => {
  $el.css('border', '3px solid red');
  cy.wait(2000); // 하이라이트 2초 동안 유지
});

4. 테스트 데이터 관리

// 테스트 데이터 팩토리 함수
// src/test/factories.js
export const createUser = (overrides = {}) => ({
  id: Math.floor(Math.random() * 10000),
  name: '테스트 사용자',
  email: `user${Math.floor(Math.random() * 10000)}@example.com`,
  role: 'user',
  ...overrides
});

export const createProduct = (overrides = {}) => ({
  id: Math.floor(Math.random() * 10000),
  name: '테스트 상품',
  price: 10000,
  description: '테스트 상품 설명',
  category: '기타',
  ...overrides
});

// 테스트에서 사용
import { createUser, createProduct } from '../test/factories';

test('사용자 정보 표시', () => {
  const user = createUser({ name: '홍길동', role: 'admin' });
  render(<UserProfile user={user} />);
  // ...
});

마무리 🎁

Jest, React Testing Library, Cypress는 프론트엔드 테스트의 효과적인 삼각편대로, 각자의 강점으로 다양한 수준의 테스트를 지원합니다. 이들을 함께 사용하면 코드 품질, 개발 속도, 그리고 무엇보다 사용자 경험을 크게 향상시킬 수 있어요!

  • Jest: 빠르고 유연한 유닛 테스트로 기본 로직 검증
  • React Testing Library: 사용자 중심의 컴포넌트 테스트로 UI 기능 검증
  • Cypress: 실제 브라우저 환경에서 E2E 테스트로 전체 사용자 흐름 검증

테스트 자동화는 단기적으로는 추가 노력이 필요하지만, 장기적으로는 버그 감소, 코드 품질 향상, 개발 자신감을 가져다줍니다. 2025년에는 테스트를 "나중에 할 것"이 아닌, 개발 과정의 핵심 부분으로 고려해보세요!

다음 시간에는 사용자 인증을 쉽게 구현할 수 있는 NextAuth.js와 Clerk에 대해 알아볼 예정이니 기대해주세요! 🔐

여러분의 프로젝트에 견고한 테스트 전략을 구축하여 더 안정적인 개발을 하시길 바랍니다! 😄👋