본 콘텐츠로 바로 이동

React 단위 테스트: 실용적인 종단 간 안내서

React 단위 테스트를 설정부터 CI/CD까지 마스터하세요. 이 안내서는 Jest, RTL, 훅, 비동기 code, 모킹 및 강력한 크로스 플랫폼 앱을 위한 최적의 관행을 다룹니다.

Martin Donadieu

Martin Donadieu

콘텐츠 마케터

React 단위 테스트: 실용적인 종단 간 안내서

점심 전 작은 UI 변경을 푸시합니다. 그것은 무해해 보입니다. 버튼 레이블이 변경되며, 조건부 렌더링이 단순화되고, 도우미 훅이 새로운 branch를 선택합니다. pull request는 깨끗하고, 리뷰는 빠르고, 배포는 진행됩니다.

1시간 후, 지원팀이 로그인 기능이 하나의 플랫폼에서 작동하지 않는다는 보고를 받습니다. 웹은 정상입니다. 데스크톱 셸에는陈舊한 렌더링 경로가 있습니다. 모바일 빌드는 비동기 상태 변경 후에 다르게 동작합니다. 아무도 그것을 잡지 못했습니다. 왜냐하면 code가 테스트를 가지고 있었지만, 올바른 테스트가 아니었고, 분명히 그런 테스트를 가진 신뢰할 수 있는 시스템도 없었습니다.

React 프로덕션 팀에서 유닛 테스트하는 가장 큰 문제입니다. 몇 개의 테스트를 작성하는 것은 어렵지 않습니다. 그러나 리팩토링, 릴리즈 트레인, 핫픽스, 크로스 플랫폼 패키징과 같은 상황에서 여전히 보호하는 테스트 스위트를 구축하는 것은 어렵습니다. React 앱은 팀이 함수를 호출하는 방법을 잊지 않았기 때문에 실패하지 않습니다. render()실패하는 이유는 테스트가 구현 세부 사항 쪽으로 치우쳐지며, 비동기 동작이 가려지며, CI가 테스트를 체크박스로 대체하는 것에 있습니다.

Modern unit testing React works when it behaves like a safety system. Fast feedback locally. Deterministic checks in CI. Clear boundaries around what belongs in a unit test and what doesn’t. That matters even more when the same React codebase ships through browsers, Capacitor containers, or Electron shells.

목차

유닛 테스트가 React에서 가장 안전한 안전망이다

유닛 테스트가 실수를 잡아내는 때는 그 실수가 발생할 수 있다고 자신있게 믿었을 때이다. React에서 일반적으로 그 의미는 컴포넌트가 여전히 렌더링되지만 사용자가 의존하는 동작이 변경된 경우이다. 비활성화된 버튼이 클릭 가능해진다. 로딩 상태가 절대 지워지지 않는다. 대체 메시지가 리팩토링 후 사라진다. 이러한 실패는 code에서 작지만 프로덕션에서 비용이 많이 들다.

React 테스트는 중요한 방식으로 바뀌었다 React Testing Library는 내부 구조에 대한 테스트 대신 동작에 대한 테스트 모델로 mainstream가 되었다이러한 shift는 React __CAPGO_KEEP_0__가 지속적으로 재배치되는 이유이다. Hooks가 이동한다. 컴포넌트가 분할된다. Context가 도입된다. 내부 구조에 대한 테스트가 건강한 리팩토링 중에 깨지지만 동작에 대한 테스트가 일반적으로 살아남는다. React Native의 테스트 지침에 반영된 것처럼, 사용자 동작을 반영하는 테스트 대신 컴포넌트 프로퍼티나 상태에 대한 테스트로 팀이 이동했다. That shift matters because React code gets rearranged constantly. Hooks move. components split. Context gets introduced. A test tied to internal structure breaks during healthy refactors. A test tied to visible behavior usually survives.

__CAPGO_KEEP_0__을 보호해야 하는 단위 테스트는 무엇인가요?

__CAPGO_KEEP_0__의 좋은 React 단위 테스트는 하나의 작은 계약을 보호합니다:

  • 렌더링된 출력: __CAPGO_KEEP_0__이 사용자가 올바른 텍스트, 레이블, 상태, 또는 기본값을 볼 수 있는지 확인합니다.
  • 인터랙션 동작: __CAPGO_KEEP_0__이 클릭, 입력, 또는 토글이 UI를 올바르게 변경하는지 확인합니다.
  • 경계 처리: __CAPGO_KEEP_0__이 예상 입력, 누락된 데이터, 또는 오류 경로를 받았을 때 올바르게 동작하는지 확인합니다.

__CAPGO_KEEP_0__의 약한 테스트는 잘못된 것을 보호합니다:

  • 컴포넌트 내부: 상태 형태, 사설 메소드, 구현 전용 속성
  • 프레임워크 메커니즘: 내부적으로 React hook이 정확히 당신이 기대하는 대로 업데이트되었는지 여부
  • 자식 정보: 이곳에서 검증하지 않으려는 중첩된 컴포넌트가 소유한 마크업

실용적인 규칙: 사용자가 보거나 할 수 있는 것을 변경하지 않고 컴포넌트를 리팩터링할 수 있다면, 테스트도 변경할 필요가 없을 것입니다.

유닛 테스트도 더 넓은 테스트 시스템 내에 위치합니다. 그들은 앱이 끝까지 작동하는지 증명하려는 것이 아닙니다. 그들은 빠른层로 regressions를 잡아내기 전에 브라우저 수준 테스트나 장치 수준 유효성 검사 패스를 필요로하지 않습니다. 그들이 의미하는 바는, 그들은 생산 앱의 자동 테스트 스택의 첫 번째 방어선입니다. React 팀이 자주 배포하는 경우, 신뢰는 이 노동의 분할에서 오릅니다. 유닛 테스트는 지역적 regressions를 빠르게 잡아냅니다. 통합 테스트는 접합점을 검증합니다. 종단간 테스트는 중요 경로를 확인합니다. 유닛层을 건너뛰면, 모든 더 느린 하위 시스템은 너무 많은 무게를 지고야 말 것입니다..

현대 React 테스트 환경을 설정하는 방법

약한 테스트 환경은 테스트를 불안정하게 만들기 전에 단 하나의 주장도 작성하지 않은 채로 발생합니다. 많은 개발자들은 Jest, jsdom, 또는 React를 비난하지만, 실제 문제는 로컬 머신과 CI에서 일관된 구성이 불일치하는 것입니다. 해결책은 환경을 재미없게 만들기입니다. 재미없다는 것은 여기서 좋은 것입니다.

정돈된 작업 공간을 특징으로 하는 컴퓨터 모니터가 React 유닛 테스트를 표시하는 __CAPGO_KEEP_0__ 에디터를 표시합니다.

A clean workspace featuring a computer monitor displaying React unit testing code in a code editor.

__CAPGO_KEEP_1__

modern React 앱, 특히 Vite로 생성된 앱의 기본 설정에는 다음이 포함되어야 합니다.

  • 테스트 러너: Jest는 특히 이전 React 코드베이스와 기업 CI 스택에서 일반적입니다.
  • 브라우저와 같은 환경: jsdom 컴포넌트 테스트가 렌더링 DOM 출력을 허용합니다.
  • 테스트 라이브러리 유틸리티: @testing-library/react 그리고 @testing-library/jest-dom
  • 싱글 셋업 엔트리 포인트: 등록 매처와 글로벌 모크를 등록하는 하나의 파일

React 테스트 지침이 강조하는 주요 워크플로는 간단합니다: jsdom-backed 환경에서 컴포넌트를 렌더링하고, getByText 또는 getByRole사용자 인터페이스를 쿼리하는 선택자와 같은 것과 같은 UI를 쿼리하고, 사용자와 상호 작용하고, DOM 변경을 확인하는 것입니다. React 테스트 문서. 그 워크플로가 신뢰할 수 있는 상태를 유지하려면 모든 머신이 동일한 테스트 환경을 실행해야 합니다.

실용적인 Jest 설정은 일반적으로 다음과 같습니다.

// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  moduleNameMapper: {
    '\\.(css|less|scss)$': 'identity-obj-proxy',
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
};

팀이 SWC를 대신하여 Babel을 사용한다면, 그건 괜찮습니다. 변환기라는 것이 중요한 것이 아닙니다. 중요한 것은 일관성입니다. 하나의 경로를 선택하고 리포지토리에 표준화하세요. 더 광범위한 JavaScript 테스트 규약에 대한 좋은 동반자 참고 자료를 원한다면 Capgo의 JavaScript 단위 테스트 가이드 팀 내에서 유용한 전달 문서입니다.

설정 파일을 추가하여 스위트가 의존하는 파일

적절한 setupTests.js 반복적인 잡음:

import '@testing-library/jest-dom';

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

이 파일은 환경 간 차이를 한 번에 해결하는 곳입니다. 테스트 파일 20개 중에 하나씩 해결하는 대신에 API mocks를 추가하여 UI가 의존하는 것과 같은 matchMedia, ResizeObserver, 또는 IntersectionObserver, 만약 컴포넌트 라이브러리가 그들을 기대한다면.

개발자들은 globals를 ad hoc로 patch한다면, 불일치한 테스트와 추적하기 어려운 실패가 발생합니다. 한 사람의 로컬 실행이 통과하는 이유는 그들이 파일에 manual mock을 추가했기 때문입니다. CI는 설정이 공유되지 않았기 때문에 실패합니다.

CI와 로컬 동작을 일치시키세요

로컬 명령어는 CI 명령어와 가능한 한 유사해야 합니다. 개발자가 watch 모드에서 permissive 기본값으로 실행하지만 CI는 더 엄격한 config로 실행한다면, merge 후 surprise 실패가 발생합니다. 스크립트를 명확하게 유지하세요:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:ci": "jest --runInBand --coverage"
  }
}

새 팀원들이 동일한 기준을 얻을 수 있도록 도와주는 짧은_walkthrough가 필요합니다:

기본값에 대한 discipline가 가장 영향력 있는 설정 선택입니다. alias를 config에 넣고, 환경 mocks를 하나의 setup 파일에 넣고, 가능할 때 UI 테스트에 대해 더 가벼운 환경을 사용하세요. 각 테스트가 필요한 custom behavior이 적을수록, 시스템이 더 신뢰할 수 있습니다. jsdom 의미 있는 컴포넌트 테스트 쓰기

조직은 테스트를 작성하는 문제가 없습니다. 여섯 개월 후에 여전히 중요하다는 것을 유지하는 테스트를 작성하는 문제가 있습니다.

React 컴포넌트의 단위 테스트에 대한 표준 패턴은 여전히 올바른 패턴입니다:

컴포넌트를 렌더링하고, 사용자 중심의 선택자로 UI를 쿼리하고, 상호 작용을 트리거하고, 결과적인 DOM 변경을 확인하는 것입니다. 이 패턴은 implementation details와 관련된 state나 props와 같은 것과 분리되도록 해줍니다. React testing guide에서 설명한 것과 같습니다.React testing guide __CAPGO_KEEP_0__. 사용 패턴을 제한된 방식으로 적용하는 trick 이다.

사용자가 사용하는 방식으로 아코디언을 테스트하세요

기본적인 Accordion 컴포넌트를 사용하세요. 버튼과 제목을 렌더링합니다. 패널 콘텐츠는 숨겨져 있습니다. 버튼을 클릭하면 콘텐츠가 표시되고 접근성 상태가 업데이트됩니다.

그것은 충분한 동작입니다. 여러 유용한 테스트를 위한 것입니다:

  1. 초기 렌더링은 제목만 표시하지만 콘텐츠는 표시하지 않습니다.
  2. 트리거를 클릭하면 콘텐츠가 표시됩니다.
  3. 클릭하면 다시 접어집니다.
  4. 접근성 속성은 표시된 상태를 반영합니다.

마지막 점은 자주 생략됩니다. 컴포넌트가 사용하는 aria-expanded, aria-controls, 또는 역할 기반 구조를 사용하는 경우, 그들을 확인하세요. 그것들은 구현 세부 사항이 아닙니다. 사용자와의 계약의 일부입니다.

최선의 컴포넌트 테스트는 받고 싶지 않은 버그 리포트와 같습니다.

__CAPGO_KEEP_0__

__CAPGO_KEEP_1__

__CAPGO_KEEP_2__ __CAPGO_KEEP_3__ __CAPGO_KEEP_4__ __CAPGO_KEEP_5__
getBy __CAPGO_KEEP_6__ __CAPGO_KEEP_7__ __CAPGO_KEEP_8__
queryBy __CAPGO_KEEP_9__ __CAPGO_KEEP_10__ null __CAPGO_KEEP_11__
findBy __CAPGO_KEEP_0__가 나타날 때 해결된다. __CAPGO_KEEP_0__가 나타나지 않으면 10초 후에 거부된다. fetch나 delayed update 후에 async-loaded 콘텐츠가 나타나야 한다는 것을 확인한다.

간단한 정신 모델을 사용하면 다음과 같이 이해할 수 있다.

  • __CAPGO_KEEP_0__가 이미 존재해야 하는 경우. getBy __CAPGO_KEEP_0__가 존재하지 않아야 하는 경우.
  • __CAPGO_KEEP_0__가 UI가 변경될 때 사용된다. queryBy 테스트가 __CAPGO_KEEP_0__로 시작하면, 대부분의 경우 컴포넌트가 업데이트될 때 어떤 시점인지 알 수 없다는 불확실성이 나중에 불안정성을 의미한다.
  • __CAPGO_KEEP_0__는 UI가 변경될 때 사용된다. findBy __CAPGO_KEEP_0__는 UI가 변경될 때 사용된다.

__CAPGO_KEEP_0__는 UI가 변경될 때 사용된다. findBy __CAPGO_KEEP_0__는 UI가 변경될 때 사용된다.

A 실제적인 접기 예시

이것은 대표적인 컴포넌트입니다:

function Accordion({ title, children }) {
  const [open, setOpen] = React.useState(false);

  return (
    <section>
      <button
        aria-expanded={open}
        aria-controls="accordion-panel"
        onClick={() => setOpen(prev => !prev)}
      >
        {title}
      </button>
      {open ? (
        <div id="accordion-panel">
          {children}
        </div>
      ) : null}
    </section>
  );
}

이것은 유지할 수 있는 테스트의 형태입니다:

import { render, screen, fireEvent } from '@testing-library/react';

test('renders the accordion title and hides content initially', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  expect(screen.getByRole('button', { name: /shipping details/i })).toBeInTheDocument();
  expect(screen.queryByText(/delivery takes 3 days/i)).not.toBeInTheDocument();
});

test('reveals content when the trigger is clicked', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  fireEvent.click(screen.getByRole('button', { name: /shipping details/i }));

  expect(screen.getByText(/delivery takes 3 days/i)).toBeInTheDocument();
});

test('updates aria-expanded when opened', () => {
  render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);

  const button = screen.getByRole('button', { name: /shipping details/i });
  expect(button).toHaveAttribute('aria-expanded', 'false');

  fireEvent.click(button);

  expect(button).toHaveAttribute('aria-expanded', 'true');
});

이것은 유지할 수 있는 테스트의 형태입니다. setOpen 내부 상태에 대한 주장, 호출된 것을 확인하는 것, 렌더링된 tree의 전체 스냅샷이 없는 것은 중요합니다.

이러한 테스트는 유지보수에만 도움이 되며, 신뢰는 아닙니다.

  • 컴포넌트 테스트를 강화하는 몇 가지 습관이 있습니다: 역할 기반 쿼리 사용하기:
  • 버튼, 헤더, 대화상자, 알림, 입력은 일반적으로 역할로 찾습니다. 테스트를 좁게 유지하기:
  • 하나의 사용자 눈에 띄는 동작을 테스트 한 테스트는 실패가 읽기 쉬워집니다. 테스트 이름은 결과에 따라 지어야 합니다:

컴포넌트가 DOM을 통해 테스트하기 어렵다면, 이는 설계 문제를 드러내는 경우가 많습니다. 상태를 올바른 위치에 숨기지 않았는지 확인하고, 의미 있는 마크업이 충분히 제공되는지 확인하는 것이 중요합니다. 좋은 테스트는 팀을 더 나은 컴포넌트로 이끄는 경우가 많습니다.

커스텀 훅과 애플리케이션 로직 테스트

리액트 앱은 컴포넌트 외부에 중요한 동작을 숨기곤 합니다. 상태 전환은 훅스 내에 존재합니다. 유효성 검사와 형식화는 도우미 함수 내에 존재합니다. 데이터 조작은 렌더링되기 전에 자주 발생합니다. 만약에 보이는 컴포넌트만 테스트한다면, 여전히 프로덕션 동작을 깨는 code를 놓치게 될 것입니다.

Hooks는 React-aware한 프레임워크를 필요로 합니다.

Capacitor를 사용하는 React 앱에서 사용하는 커스텀 훅은 React가 제대로 작동하기 위해 여전히 React를 필요로 합니다. 따라서 그것을 테스트하기 위해 Note: I've kept the placeholder __CAPGO_KEEP_0__ as is. renderHook state 변경을 위한 호출을 감싸세요. act().

작은 useToggle hook은 좋은 예시입니다.

import { useState, useCallback } from 'react';

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = useCallback(() => setValue(current => !current), []);
  return { value, toggle };
}

그의 테스트는 공공 계약에 집중해야 한다.

import { renderHook, act } from '@testing-library/react';
import { useToggle } from './useToggle';

test('returns the initial value', () => {
  const { result } = renderHook(() => useToggle(true));
  expect(result.current.value).toBe(true);
});

test('toggles the value', () => {
  const { result } = renderHook(() => useToggle(false));

  act(() => {
    result.current.toggle();
  });

  expect(result.current.value).toBe(true);
});

그 테스트는 유용하다. 왜냐하면 훅 자체가 단위이기 때문이다. React 내부를 테스트하는 것이 아니라 훅의 외부 동작을 확인하는 것이다.

이 패턴은 재사용 가능한 UI 또는 기능 원초를 구축하는 제품 팀에게 매우 중요합니다. Hooks는 종종 앱, 디자인 시스템, 또는 내부 도구 간에 공유되는 인터페이스가 됩니다. 재사용 가능한 동작을 설계할 때 商業적 의도와 함께, makers' 제품에 대한 hook 함수는 구현 세부 사항 대신 제품화된 빌딩 블록으로 프레임워크를 도울 수 있습니다.

순수 논리는 테스트에서 순수하게 유지되어야 합니다.

모든 것이 React, 또는 Testing Library가 필요하지 않습니다. jsdom순수 함수의 경우, Node 환경에서 평범한 Jest로 테스트하세요.

예시:

export function formatDisplayName(firstName: string, lastName: string) {
  return `${firstName.trim()} ${lastName.trim()}`.trim();
}

이 테스트는 매우 단순해야 합니다.

import { formatDisplayName } from './formatDisplayName';

test('joins and trims both names', () => {
  expect(formatDisplayName(' Ada ', ' Lovelace ')).toBe('Ada Lovelace');
});

test('handles a missing last name', () => {
  expect(formatDisplayName('Ada', '')).toBe('Ada');
});

이 승리에서 속도와 명확성이 있습니다.

함수가 렌더링된 tree가 필요하지 않다면, 그 것을 제공하지 마세요.

  • React 관련 도구는 오버헤드를 추가합니다. 비즈니스 로직 테스트는 작고 빠르게 유지하세요. renderHook, act()실용적인 분리는 잘 작동합니다.
  • Hook: 사용할 때만 사용하세요, wrapper provider, 및 필요할 때만 사용하세요. plain Jest과 DOM을 사용하지 마세요.
  • 상태를 유지하는 교차 로직: 컴포넌트 테스트가 너무 많은 로직을 수행할 때 테스트 가능한 헬퍼로 끌어들이세요.

팀들은 종종 컴포넌트 테스트에 로직을 포함시키는데, 그 로직은 스택의 아래쪽에 속하는 로직이다. 그 로직을 끌어내면 두 가지 이점이 있습니다. 컴포넌트 테스트가 더 깨끗해지며, 로직 테스트가 더 빠르게 수행됩니다.

고급 기술을 마스터하세요. Mocking과 Async

대부분의 불신스러운 React 테스트 스위트는 두 가지 장소에서 깨집니다. 의존성 경계에서 깨집니다, 그리고 시간과 관련된 장소에서 깨집니다.

그것이 왜 async 테스트와 mocking이 신뢰할 수 있는 테스트 스위트와玩具 테스트 스위트를 구분하는 선을 그리는 것인지 이유가 있습니다. 하나의 분석은 46.5%의 테스트 불안정성은 환경 또는 자원과 관련된 문제로 인해 발생합니다. 예를 들어, async 타이밍과 같은 것들입니다. 이 React 단위 테스트 분석에서 React 앱에서, 그것은 직접적으로 상태 전환, 지연 렌더링, 네트워크에 의한 UI, 그리고 테스트가 결정적으로 기다리지 않고 추측하는 테스트와 관련이 있습니다.Mocking Dependencies versus Asynchronous Testing에 초점을 맞춘 Advanced React Testing Techniques 비교 차트입니다.

__CAPGO_KEEP_0__

Mocking 경계를 설정하십시오, 모든 layer를 Mock하지 마십시오

빠른 속도로 잘못된 테스트를 작성하는 가장 빠른 방법은 반쪽의 컴포넌트 tree를 Mock하고 나중에 Mock된 Mock이 작동했는지 확인하는 것입니다.

계정 데이터를 가져오는 컴포넌트의 경우 네트워크 클라이언트 또는 API 모듈을 Mock하십시오. Hook, 자식 row 컴포넌트, 로딩 스피너, 세 가지 유틸리티 함수를 Mock하지 마십시오. Mock이 필요하지 않은 경우에만 Mock하십시오.

이 규칙 집합을 사용하십시오:

  • 외부 서비스를 Mock하십시오: HTTP 클라이언트, 분석, 브라우저 전용 API, 네이티브 브리지
  • 불안정한 플랫폼 API를 Mock하십시오: matchMedia타이머, Electron preload 인터페이스, Capacitor 플러그인(JavaScript DOM에서 사용할 수 없는 경우)
  • 기본적으로 자신의 내부를 Mock하지 마십시오: 커스텀 Hook, 간단한 자식, 로컬 유틸리티

모든 어려운 부분이 가짜로 대체된 테스트가 통과하면, 이는 많은 릴리즈에 대한 신뢰를 얻은 것이 아닙니다.

Runner API에 대한 예제 및 패턴을 찾고 있는 팀에게 Capgo 테스트 가이드 개발자가 React를 알고 있지만 테스트 메커니즘을 아직 모르는 경우, 특히 입문자에게는 실용적인 참고 자료입니다.

동기 테스트는 시간이 모호할 때 실패합니다.

동기 테스트가 실패하는 경우 일반적으로 세 가지 오류 중 하나 때문입니다.

  1. 테스트가 너무 일찍 확인하는 경우입니다.
  2. 테스트가 임의의 타이머를 사용하여 기다리는 경우입니다.
  3. 컴포넌트가 여러 번 업데이트되지만 테스트가 단일 전환만 모델링하는 경우입니다.

안정적인 동기 테스트는 일반적으로 다음 형태를 가집니다.

test('shows user details after data loads', async () => {
  render(<UserProfile userId="42" />);
  expect(screen.getByText(/loading/i)).toBeInTheDocument();

  expect(await screen.findByText(/account owner/i)).toBeInTheDocument();
});

또는 특정 조건을 기다릴 때 필요할 때는:

await waitFor(() => {
  expect(screen.getByRole('alert')).toBeInTheDocument();
});

이벤트가 발생할 때 특정 요소의 모습이 관심사일 때 사용하세요. findBy 조건이 더 넓거나 상태를 단일 쿼리로 표현할 수 없는 경우 사용하세요. waitFor 사용하지 마세요. setTimeout 테스트에서만 사용하는 경우 timer 동작을 명시적으로 테스트하고 가짜 타이머를 사용하는 경우를 제외하고.

리액트의 테스트 생태계도 업데이트에 대한 의미를 존중하는 것을 기대합니다. 테스트 라이브러리는 많은 부분을 처리하지만, 상태를 수동으로 조정하거나 타이머를 앞서가면 여전히 업데이트가 플러시되는 시점에 대해 생각해야 합니다. act() 어떤 mocking 도구를 사용할지 알기

다양한 mocking 도구는 서로 다른 문제를 해결합니다:

도구

최적의 사용 일반적인 오류 독립적인 가짜 콜백 또는 주입 함수
jest.fn() 단순한 콜백만으로 충분한 경우에 모듈 전체를 대체하는 경우 실제 객체 또는 모듈의 한 메서드를 관찰하거나 오버라이드하는 경우
jest.spyOn() 원래 구현을 복원하지 않는 경우 __CAPGO_KEEP_0__
jest.mock() 모듈 의존성을 임포트 경계에서 교체하십시오. 대량 모듈을 기본적으로 모킹하고 의미 있는 동작을 잃습니다.

예시 도움말:

  • Reach for jest.fn() 컴포넌트가 onSubmit 속성을 받을 때.
  • __CAPGO_KEEP_0__를 내보내는 함수를 확인할 때 사용하세요. jest.spyOn() __CAPGO_KEEP_0__를 내보내는 함수를 확인할 때 사용하세요. console.errorAPI를 내보내는 함수를 확인할 때 사용하세요.
  • 오류 경로 테스트는 현대 React에서 많은 가이드들이 부족한 영역입니다. 오류 경계, 지연된 상태 변경, 비동기 리커버리 UI가 최적의 테스트를 deserve합니다. 자식이 예외를 발생시키면 리커버리 UI를 확인하세요. 요청이 실패하면 표시되는 리커버리 상태를 확인하세요. 로딩 중인 버튼이 비활성화되면 그 상태도 확인하세요. 사용자가 기억하는 버그입니다. jest.mock() code를 내보내는 함수를 확인할 때 사용하세요.

__CAPGO_KEEP_0__를 내보내는 함수를 확인할 때 사용하세요.

테스트 품질과 전략을 개선하는 방법

많은 팀이 여전히 신뢰와 같은 것으로 보는 커버리지를 추구하고 있습니다. 그것은 아님.

커버리지 목표를 달성할 수 있고도 회귀 테스트를 놓치게 될 수 있습니다. 얕은 어설션, 광범위한 스냅샷, 내부 모킹을 포함하는 테스트 스위트는 안전성이 보이게 하지만 유지 보수 비용을 증가시킵니다.

품질 테스트의 이점과 고량 테스트 스위트의 유지 보수 오버헤드 비교를 보여주는 그래픽.

커버리지가 목표가 아닌 지도입니다.

커버리지 보고서는 다음 질문에 답할 때 유용합니다: 아직 보호되지 않은 중요한 경로가 무엇입니까?

개발자들을 트리비아 랩퍼, 정적 마크업, 또는 한 줄의 통과 파일만 테스트하기 위해 퍼센티지 이동을 위해 밀어내는 것은 유용하지 않습니다. 커버리지를 발견 도구로 다루세요. 인증 상태, 청구 액션, 기능 플래그, 또는 업데이트 프롬프트가 테스트되지 않은 경우, 그 신호입니다. 사용자 눈에 띄는 아이콘 컴포넌트가 테스트되지 않은 경우, 일반적으로 아님.

건강한 검토 질문은 간단합니다: 이 테스트가 릴리스 위험을 줄이는가?

  • 예: 사용자 눈에 띄는 동작을 검증하는 것입니다.
  • 아니요: 이 테스트는 비즈니스 논리를 보호하지 않습니다. 쉽게 리팩토링 중에 깨질 수 있습니다.
  • 아니요: 구현 세부 사항을 명시하거나 다른 테스트의 값을 복제하는 테스트입니다.

어떤 것을 단위 테스트하지 않는가

React에 대한 많은 가이드는 여전히 생략에 충분한 시간을 보내지 않습니다. 이 빈틈은 중요합니다. 왜냐하면 과도한 모킹과 구현 세부 사항 테스트는 사용자 경험을 깨트리는 동안 테스트가 통과하는 brittle한 테스트 스위트를 만듭니다. 이는 BrowserStack의 React에서 단위 테스트하지 않는 것에 대한 가이드에서 언급한 바와 같이 다음 패턴을 생략하거나 엄격하게 제한하세요:.

내부 상태 확인:

  • 직접 테스트하지 말고, 패널이 열렸는지 여부를 테스트하세요. 프레임워크 동작: isOpen React가 효과를 호출했는지 테스트하지 말고, 효과가 변경한 결과를 테스트하세요.
  • 세 번째-party 라이브러리 내부: Don’t test that React called an effect. Test the result of what the effect changes.
  • Third-party library internals: 날짜 선택기나 라우터와 통합 테스트를 하세요. 라이브러리의 렌더링 로직이 아닌.
  • 잘못된 단위: 모든 자식과 헬퍼를 모킹했다면, 더 이상 의미 있는 동작을 테스트하지 못할 수 있습니다.

잘못된 테스트는 리팩토링을 막고 여전히 프로덕션 버그를 잡지 못하는 테스트보다 나쁩니다.

유용한 지침은 경계 소유입니다. code가 소유한 것을 테스트하세요. React, 브라우저, 또는 성숙한 라이브러리가 이미 소유한 것을 테스트하지 마세요. 단, 통합層이 계약을 변경한다면.

샷샷이 도움이 되는 곳과 해가 되는 곳

샷샷은 쓸모가 없습니다. 단순히 잘못 사용하기 쉽기 때문입니다.

샷샷은 구조적 diff가 의미 있는 컴포넌트에만 사용하세요. 반응형 또는 동적이 아닌 컴포넌트에 사용하면 노이즈가 됩니다. 개발자는 그들을 읽지 않고 자동으로 업데이트하기 시작합니다.

대안이 보통 있습니다:

  • 조건부 렌더링을 테스트할 때, 키 텍스트의 존재 또는 비존재를 확인하세요.
  • 시각적 상태 변경을 테스트할 때, 중요하다고 여기는 역할, 레이블, 또는 속성을 확인하세요.
  • 에러와 폴백을 테스트할 때, 실제 메시지 또는 알림 영역을 확인하세요.

If your team needs a broader quality process beyond unit tests, a solid companion is an __CAPGO_KEEP_0__ 애플리케이션 품질 보증 워크플로우 tests, release checks, and rollback planning을 하나의 시스템으로 다루는

Stop asking how many tests you have. Start asking which failures could still reach users.

Cross-Platform CI/CD Pipeline에 테스트 통합

A test suite that only runs on a developer laptop is a suggestion, not a control.

clean environment에서 동일한 체크를 실행하고 실패 시 병합을 차단하는

That sounds obvious, but many teams still leave critical gaps.

Tests run manually. Coverage reports are optional. Packaging and release jobs start before test jobs have finished.

  • A five-step flowchart illustrating the process of integrating React automated tests into a CI/CD development pipeline.
  • A pull request should trigger the same gate every time
  • For unit testing React to act like a safety net, CI needs a few essentials: "Run on every pull request" "Install dependencies from lockfile" "Use the same test command every time"
  • 테스트 실패 시 빠르게 실패하라
  • 테스트가 통과한 후에만 artifact를 배포하라

이것이 앱 팀을 위한 지속적인 배포 연습의 핵심이다릴리즈 전에 자신감을 얻으려는 것이 아니라, 릴리즈 후에 얻으려는 것이다.

많은 팀에게는 간단한 GitHub Actions 워크플로만 필요하다.

name: test

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  react-tests:
    runs-on: ubuntu-latest

    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm run test:ci

이것은 멋지지 않다, 그리고 그게 목적이다. 가장 강력한 pipeline은 일반적으로 가장 놀라지 않는 pipeline이다.

Capacitor와 Electron에 대한 이유

크로스 플랫폼 React 앱은 브라우저 전용 앱보다 릴리즈 리스크가 더 높다. 왜냐하면 같은 UI code가 종종 다른 컨테이너에 다른 런타임 가정과 함께 배포되기 때문이다.

몇 가지 예시를 통해 pipeline이 도움이 되는지 보여준다.

  • Capacitor 앱: 웹 code가 로컬에서 통과하지만 플러그인 브리지, 오프라인 상태, 앱 라이프 사이클 에지 케이스 등이 패키징 후에 동작을 변경할 때 실패할 수 있다.
  • Electron 앱: 렌더러 컴포넌트는 브라우저 테스트에서 존재하지 않는 preload API, 윈도우 메시징, 또는 데스크톱 전용 상태에 의존할 수 있습니다. 이 경우에는 의도적으로 모킹해야 합니다.
  • 공유 릴리스 트레인: 하나의 잘못된 패키지로 여러 대상에 영향을 줄 수 있습니다. 만약 배포 프로세스가 엄격하게 배포를 게이트로 하지 않는다면.

그것은 단위 테스트가 패키징 작업 전에 실행되어야 하며, 패키징 작업이 배포 작업 전에 실행되어야 한다는 것을 의미합니다. 각 단계는 위험을 줄입니다. 단위 테스트는 지역적인 회귀를 빠르게 잡을 수 있습니다. 플랫폼 패키징은 환경 가정의 검증을 수행합니다. 마지막 릴리스에 대한 신뢰를 처리하는 것은 수동 승인 또는 스테이지 롤아웃입니다.

실용적인 GitHub Actions 워크플로우

더 성숙한 PIPELINE은 책임을 나누어야 합니다.

  1. 테스트 작업: 빠른 단위 및 훅 테스트
  2. 빌드 작업: 테스트가 통과한 후에만 프로덕션 빌드를 생성합니다.
  3. 패키징 작업: Capacitor 동기화, Electron 패키징, 또는 아티팩트 번들링
  4. Release 작업: 만약에 승인된 branch 또는 tag에서만 배포하도록 설정합니다.

Capgo를 사용하는 팀은 Capacitor 또는 Electron 앱에 실시간 업데이트를 배포하는 경우, 이곳에서 release tooling이 중요합니다. 그 workflow에서 하나의 옵션은 Capgo, CapacitorJS 및 Electron 앱에 서명된 웹 번들을 배포하는 데 rollback 지원과 채널 기반의 배포 제어를 제공합니다. 실제로, React 테스트 작업은 웹 번들이 스테이징 또는 프로덕션 배포로 승격되기 전에 첫 번째 강제 게이트 역할을 할 수 있습니다.

작업 절차는 간단합니다. release 인프라가 weak 테스트를 보상하지 않도록 하세요. release 인프라를 사용하기 전에 reliable 테스트가 이미 bad 변경 사항을 필터링한 후에 사용하세요.

신뢰할 수 있는 테스트 시스템은 팀 행동을 변경합니다. 개발자들은 merge에 더 많은 자신감을 가지고 있습니다. 리뷰어들은 edge 케이스에 집중하기보다는 기본적인 것들을 수동으로 다시 실행하지 않습니다. release 매니저들은 배포를 gamble로 다루지 않습니다. React를 잘 테스트하는 것이 unit testing의 결과입니다.


Capacitor를 사용하는 팀은 React를 Capacitor 또는 Electron을 통해 배포하는 경우, release 안전은 green local 테스트만으로는 충분하지 않습니다. Capgo 는 팀에게 signed 웹 업데이트를 배포하는 controlled 방법, rollout 채널을 지정하고, bad 번들을 rollback하는 방법을 제공합니다. store 리뷰를 기다리지 않고, CI pipeline이 이미 배포 전에 unit 테스트를 통과해야 하는 경우에 자연스럽게 적합합니다.

Capacitor 앱에 대한 실시간 업데이트

Capgo를 통해 웹-layer 버그를 즉시 수정하고 앱 스토어 승인 대기 없이 배포할 수 있다. 사용자는 배경에서 업데이트를 받으면서 네이티브 변경은 일반적인 검토 경로에 남아있다.

시작하기

블로그에서 최신 뉴스

Capgo은 전문적인 모바일 앱을 만들기 위해 필요한 최고의 통찰력을 제공한다.