UI 변경을 위해 점심 전까지 작은 변경을 푸시합니다. 그것은 무해해 보입니다. 버튼 레이블이 변경되고 조건부 렌더링이 단순화되고 도우미 훅이 새로운 branch를 선택합니다. pull request는 깨끗하고 리뷰는 빠르고 배포는 진행됩니다.
1시간 후, 지원팀이 로그인 기능이 하나의 플랫폼에서 작동하지 않는다고 보고합니다. 웹은 정상적으로 작동합니다. 데스크톱 셸에는陈舊한 렌더링 경로가 있습니다. 모바일 빌드는 비동기 상태 변경 후에 다르게 동작합니다. 아무도 그것을 잡지 못했습니다. 왜냐하면 code에는 테스트가 있었지만, 올바른 테스트가 아니었고, 그 테스트에 대한 신뢰할 수 있는 시스템도 없었습니다.
React 앱이 실패하는 이유는 팀이 __CAPGO_KEEP_0__을 호출하는 방법을 잊었기 때문이 아닙니다. 그들은 실패하는 이유는 테스트가 구현 세부 사항에 기울어지기 때문입니다. 비동기 동작이 가려지기 때문입니다. CI는 테스트를 체크박스로만 다루기 때문입니다. render()React 앱이 실패하는 이유는 팀이 __CAPGO_KEEP_0__을 호출하는 방법을 잊었기 때문이 아닙니다. 그들은 실패하는 이유는 테스트가 구현 세부 사항에 기울어지기 때문입니다. 비동기 동작이 가려지기 때문입니다. CI는 테스트를 체크박스로만 다루기 때문입니다.
Unit Testing React은 안전 시스템처럼 작동할 때만 효과적입니다. 로컬에서 빠른 feedback. CI에서 결정적인 검사. 단위 테스트에서 무엇이 포함되어야 하는지, 무엇이 포함되지 shouldn't인지 명확한 경계를 설정하는 것이 중요합니다. 이것은 브라우저, Capacitor 컨테이너, 또는 Electron 셸을 통해 동일한 React 코드베이스를 배포할 때 더 중요합니다.
Table of Contents
- Unit Testing React은 당신의 가장 안전한 안전망입니다
- 최신 리액트 테스트 환경 설정
- 의미 있는 컴포넌트 테스트 작성
- 커스텀 훅스와 애플리케이션 로직 테스트
- 고급 기술 마킹과 비동기 테스트
- 테스트 품질과 전략을 개선하는 방법
- 다양한 플랫폼 CI/CD pipeline에 테스트 통합하는 방법
__CAPGO_KEEP_0__ 단위 테스트는 안전망이 됩니다
단위 테스트는 오류를 잡아내서 자신감이 있었던 오류를 잡아내서 그만큼의 가치를 얻습니다. React에서 일반적으로 이는 컴포넌트가 렌더링되지만 사용자가 의존하는 동작이 변경된 경우입니다. 비활성화된 버튼이 클릭 가능해지거나 로딩 상태가 지속되거나 대체 메시지가 리팩토링 후에 사라지게 됩니다. 이러한 실패는 작은 오류로 code에서 비용이 들지만 실제 배포에서는 비용이 많이 들 것입니다.
React 테스트는 중요한 방식으로 바뀌었는데 React Testing Library가 사용자 동작 대신 내부 구조에 대한 테스트 대신 mainstream 모델로 바뀌면서 팀은 사용자 동작을 반영하는 테스트 대신 컴포넌트 속성이나 상태에 대한 테스트를 하게 되었습니다. React Native의 테스트 지침이 React Native 테스트 개요 에 반영되어 있습니다. 이러한 shift는 중요합니다. React code가 지속적으로 재배치되기 때문입니다. Hooks가 이동하거나 컴포넌트가 분리되거나 Context가 추가되면 내부 구조에 의존하는 테스트가 정상적인 리팩토링 중에 깨지게 됩니다. 사용자에게 보이는 동작에 의존하는 테스트는 일반적으로 리팩토링을 통해 살아남습니다.
단위 테스트가 보호해야 하는 것
좋은 React 단위 테스트는 하나의 작은 계약을 보호해야 합니다.
- 렌더링된 출력: 사용자가 올바른 텍스트, 레이블, 상태, 또는 대체 메시지를 볼 수 있는지 확인합니다.
- 인터랙션 동작: 클릭, 입력, 또는 토글이 UI를 올바르게 변경하는지 확인합니다.
- 경계 처리: 컴포넌트가 예상 입력, 누락된 데이터 또는 오류 경로를 받았을 때 올바르게 동작하는지 여부
약한 테스트는 잘못된 것을 보호한다:
- 컴포넌트 내부: 상태 형태, 사설 메소드, 구현 전용 props
- 프레임워크 메커니즘: React가 내부적으로 정확하게 예상한 대로 hook을 업데이트했는지 여부
- 자식 세부 사항: 중첩된 컴포넌트에서 검증하지 않으려는 마크업
실용적인 규칙: 사용자가 보거나 할 수 있는 것을 변경하지 않고 컴포넌트를 리팩터링할 수 있다면, 테스트도 변경할 필요가 없다는 것이다.
유닛 테스트도 더 광범위한 테스트 시스템에 속한다. 그들은 앱이 종단 간으로 작동하는지 증명하려는 것이 아니다. 그들은 브라우저 수준 테스트나 장치 수준 유효성 검사 패스를 필요로 할 때까지 회귀를 잡아내는 빠른 층이다. 따라서 합리적인 스택의 첫 번째 방어선이다. 생산 앱에 대한 자동 테스트.
React 팀이 자주 배포할 때, 신뢰는 이 노동 분배의 결과입니다. 단위 테스트는 지역적인 회귀를 빠르게 잡습니다. 통합 테스트는 접합부를 확인합니다. 종단 간 테스트는 중요 경로를 확인합니다. 단위层을 건너뛰고, 모든 다운스트림의 속도가 너무 느려서 너무 많은 무게를 지게 됩니다.
모던 React 테스트 환경 설정
단일 어설션을 작성하기 전에 flaky 테스트가 발생하는 불안정한 테스트 환경을 만들면 안됩니다. 많은 개발자들은 Jest, jsdom, 또는 React를 문제의 원인으로 지목하지만, 실제 문제는 로컬 머신과 CI에서 불일치하는 구성이 있습니다. 해결책은 환경을 재미없게 만들기입니다. 재미없다는 것은 여기서 좋은 것입니다.

예측 가능한 러너와 환경으로 시작하세요
Vite로 생성된 최신 React 앱의 기본 설정으로 시작하세요.
- 테스트 러너: Jest는 특히 이전 React 코드베이스와 기업 CI 스택에서 여전히 일반적입니다.
- 브라우저와 같은 환경:
jsdom컴포넌트 테스트가 DOM 출력을 렌더링할 수 있도록 합니다. - 테스트 라이브러리 유틸리티:
@testing-library/react및@testing-library/jest-dom - 단일 설정 엔트리 포인트: 등록 매처와 글로벌 모킹을 하나의 파일에 등록하는 방법
React의 테스트 지침을 강조하는 주요 워크플로는 간단합니다: jsdom-backed 환경에서 컴포넌트를 렌더링하고, UI를 선택자와 같은 getByText 또는 getByRole, 사용자와 상호 작용을 유도하고, DOM 변경을 확인하는 것과 같은React 테스트 문서
에 설명된 것과 같습니다.
// 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',
},
};
If your team uses SWC instead of Babel, that’s fine. The point isn’t the transformer. The point is consistency. Choose one path and standardize it in the repo. If you want a good companion reference for broader JavaScript testing conventions, Capgo’s 실용적인 Jest 설정은 일반적으로 다음과 같습니다: 만약 팀이 SWC 대신 Babel을 사용한다면, 그건 괜찮습니다. 변환기라는 것이 중요한 것이 아니고, 일관성입니다. 하나의 경로를 선택하고, 저장소에서 표준화하세요. 더 광범위한 자바스크립트 테스트 규약에 대한 좋은 동반자 참고 자료를 원한다면, __CAPGO_KEEP_0__의
설치 파일을 추가하여 스위트가 의존하는 파일을 지정합니다.
적절한 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에 의존하는 UI에 대한 모킹을 추가합니다. matchMedia, ResizeObserver, 또는 IntersectionObserver, 만약 컴포넌트 라이브러리가 그들을 기대한다면.
이것이 없으면 개발자는 전역 변수를 임시로 수정합니다. 이것은 불일치 테스트와 추적하기 어려운 실패를 생성합니다. 한 사람의 로컬 실행이 통과한다는 것은 그들이 파일에 수동 모킹을 추가했기 때문입니다. CI가 실패한다는 것은 설정이 공유되지 않았기 때문입니다.
로컬 및 CI 동작을 일치시킵니다.
로컬 명령어는 CI 명령어와 가능한 한 유사해야 합니다. 개발자가 watch 모드에서 관대한 기본값으로 실행하지만 CI가 더 엄격한 구성으로 실행한다면, 병합 후에 놀라운 실패가 발생합니다. 스크립트를 명확하게 유지합니다:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand --coverage"
}
}
짧은_walkthrough가 새로운 팀원들이 동일한 기준을 빠르게 얻을 수 있도록 도와줍니다:
설정에서 가장 영향력 있는 선택은 기본값에 대한 discipline입니다. Aliases를 config에 넣고, 환경 모킹을 하나의 설정 파일에 넣고, UI 테스트에 대해 jsdom 를 사용하고, 가벼운 환경을 pure utility에 사용할 때 가능하면 사용합니다. 각 테스트가 필요한 custom behavior이 적을수록 시스템이 더 신뢰할 수 있습니다.
컴포넌트 테스트를 의미 있게 작성하는 방법
회사들은 테스트를 작성하는 문제가 없다. 문제는 여섯 달 후에도 의미 있는 테스트를 작성하는 문제다.
리액트 컴포넌트의 단위 테스트를 위한 표준 패턴은 여전히 올바른 패턴이다. 컴포넌트를 렌더링하고, 사용자 중심의 선택자로 UI를 쿼리하고, 상호 작용을 유발하고, 결과적인 DOM 변경을 확인하는 것이 패턴은 구현 세부 사항인 상태나 props와 같은 것과 관련이 없는 것을 유지하기 위해, 리액트 테스트 가이드 에서 설명한 것과 같이.
그런 패턴을 제한된 방식으로 적용하는 것이 중요하다.
사용자가 사용하는 것처럼 아코디언을 테스트한다. Accordion 기본적인
컴포넌트를 취한다. 버튼과 제목이 렌더링된다. 패널 콘텐츠는 숨겨져 있다. 버튼을 클릭하면 콘텐츠가 드러나고 접근성 상태가 업데이트된다.
- 이러한 행동만으로도 유용한 여러 테스트를 작성할 수 있다:
- __CAPGO_KEEP_0__을 클릭하면 내용이 나타납니다.
- __CAPGO_KEEP_0__을 다시 클릭하면 축소됩니다.
- 접근성 속성은 보이는 상태를 반영합니다.
마지막 점은 자주 생략되는 부분입니다. 컴포넌트가 사용하는 구조가 role-based 구조 또는 aria-속성을 사용한다면 확인해 주세요. 사용자와의 계약이기 때문입니다. aria-expanded, aria-controls최선의 컴포넌트 테스트는 받고 싶지 않은 버그 리포트와 같습니다.
테스트 쿼리는 의도에 따라 선택해야 합니다.
React Testing Library는 여러 가지 쿼리 스타일을 제공하지만 서로 교환할 수 없습니다. 올바른 쿼리를 선택하지 않으면 테스트가 노이즈나 오해를 일으킬 수 있습니다.
쿼리 유형
| 요소가 발견될 때 | 요소가 발견되지 않을 때 | 사용 사례 예시입니다. | __CAPGO_KEEP_1__ |
|---|---|---|---|
getBy | __CAPGO_KEEP_0__을 즉시 반환합니다. | __CAPGO_KEEP_0__은 즉시 오류를 발생시킵니다. | __CAPGO_KEEP_0__은 버튼이나 헤딩이 이미 화면에 나타나야 한다는 것을 확인합니다. |
queryBy | __CAPGO_KEEP_0__을 즉시 반환합니다. | __CAPGO_KEEP_0__ null | __CAPGO_KEEP_0__은 화면에 나타나기 전에 숨겨진 콘텐츠가 존재하지 않아야 한다는 것을 확인합니다. |
findBy | __CAPGO_KEEP_0__이 나타나면 해결됩니다. | __CAPGO_KEEP_0__이 기다린 후 거부합니다. | __CAPGO_KEEP_0__은 fetch 또는 지연 업데이트 후에 나타나는 콘텐츠가 나타났는지 확인합니다. |
간단한 정신 모델이 도움이 됩니다.
- __CAPGO_KEEP_0__을 사용하여
getBy__CAPGO_KEEP_0__이 이미 존재해야 합니다. - 사용하여 아직 존재하지 않아야 하는 것들을 사용하세요.
queryBy__CAPGO_KEEP_0__ - __CAPGO_KEEP_0__
findByUI가 나중에 변경될 때 사용하세요.
__CAPGO_KEEP_1__ findBy 테스트가 __CAPGO_KEEP_1__로 시작하면, 일반적으로 컴포넌트가 업데이트되는 시점에 대한 저자의 불확실성이 나중에 불안정성을 의미합니다.
실용적인 아코디언 예시
이러한 대표적인 컴포넌트를 보세요:
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');
});
그것이 빠진 것은 그만큼 중요합니다. 내부 상태에 대한 주장, __CAPGO_KEEP_2__가 호출되었는지 확인, 렌더링된 tree의 전체 스냅샷과 같은 테스트는 신뢰를 증가시키기 보다는 유지 관리를 증가시킵니다. setOpen 컴포넌트 테스트를 강화하는 몇 가지 습관이 있습니다:
__CAPGO_KEEP_3__
- 역할 기반 쿼리를 선호하세요: 버튼, 제목, 대화상자, 경고, 입력란은 일반적으로 역할로 찾을 수 있습니다.
- 테스트를 좁게 유지하세요: 하나의 사용자 눈에 띄는 동작을 테스트하는 테스트 하나로 실패를 읽기 쉽게 유지하세요.
- 테스트 이름은 결과에 따라 지어주세요: “updates aria-expanded when opened” is much more useful than “works correctly.”
컴포넌트가 DOM을 통해 테스트하기 어렵다면, 그게 디자인 문제를 드러내는 것입니다. 상태를 잘못된 곳에 숨기고 있거나, 의미 있는 마크업을 부족하게 사용하고 있는 것입니다. 좋은 테스트는 팀을 더 나은 컴포넌트로 이끄는 경우가 많습니다.
커스텀 훅스와 애플리케이션 로직 테스트
리액트 앱은 중요한 동작을 컴포넌트 외부에 숨기곤 합니다. 상태 전환은 훅스에, 유효성 검사와 형식화는 도우미 함수에, 데이터 조작은 렌더링하기 전에 발생합니다. 만약에만 표시되는 컴포넌트만 테스트한다면, 여전히 프로덕션 동작을 깨는 code를 놓치게 될 것입니다.
훅스는 리액트에 대한 지식을 가진 해쉬를 필요로 합니다.
커스텀 훅스는 리액트가 실행될 수 있도록 해야 하므로, renderHook 상태 변경을 호출하는 것을 wrap하세요. act().
A small hook is a good example: useToggle 그것의 테스트는 공공 계약에 집중해야 합니다:
import { useState, useCallback } from 'react';
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(current => !current), []);
return { value, toggle };
}
그 테스트는 유용합니다. 왜냐하면 hook 자체가 단위입니다. React 내부를 테스트하는 것이 아닙니다. hook의 외부 동작을 확인하는 것입니다.
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);
});
품질 팀이 재사용 가능한 UI 또는 기능 원초를 빌드하는 경우 이 패턴은 매우 중요합니다. Hooks는 종종 앱, 디자인 시스템 또는 내부 도구 간에 공유되는 인터페이스가 됩니다. 만약 재사용 가능한 동작을 상업 목적으로 설계하는 경우,
makers' 제품에 대한 hook에 대한 리소스 can help frame hooks as productized building blocks rather than just implementation details. 순수 논리는 테스트에서 순수해야 합니다.
모든 것이 필요하지 않습니다.
React, 또는 Testing Library가 필요하지 않습니다. 만약 함수가 순수하다면, Node 환경에서 plain Jest로 테스트하세요. jsdom예시:
그 테스트는 매우 단순해야 합니다:
export function formatDisplayName(firstName: string, lastName: string) {
return `${firstName.trim()} ${lastName.trim()}`.trim();
}
__CAPGO_KEEP_0__
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');
});
The win here is speed and clarity. When a function doesn’t need a rendered tree, don’t give it one. React-specific tooling adds overhead. Keep business logic tests small, fast, and close to the function they verify.
A practical split works well:
- Hooks: Use __CAPGO_KEEP_0__ and __CAPGO_KEEP_1__ when needed.
renderHook,act()Utilities: - Use plain Jest and no DOM. Stateful cross-cutting logic:
- Pull it into testable helpers when the component test starts doing too much. Teams often overstuff component tests with logic assertions that belong lower in the stack. Pulling that logic out gives you two benefits. The component test gets cleaner, and the logic test gets faster.
Mastering Advanced Techniques Mocking and Async
Most unreliable React suites break in two places. They break at dependency boundaries, and they break around time.
__CAPGO_KEEP_2__
That’s why async testing and mocking are the dividing line between a toy test suite and one you can trust before release. One analysis attributes 46.5%의 테스트 불안정성은 환경 또는 자원 관련 문제로 인한 async 타이밍 in 이 React 단위 테스트 분석. In React 앱에서, 그게 바로 상태 전환, 지연 렌더링, 네트워크 기반 UI, 테스트가 대신 기다리지 않고 추측하는 경우에 해당한다.

Mock the boundary, not every layer
가장 빠른 방법으로 잘못된 테스트를 작성하는 것은 컴포넌트 tree의 절반을 mock하고, 그리고 assert하는 것이다.
For a component that fetches account data, mock the network client or API module. Don’t mock the hook, the child row component, the loading spinner, and three utility functions unless the test truly needs isolation at those seams.
이 규칙 세트를 사용하라:
- Mock external services: HTTP 클라이언트, 분석, 브라우저 전용 API, 네이티브 브리지
- Mock 불안정한 플랫폼 API:
matchMedia, 타이머, Electron 프리로드 인터페이스, Capacitor 플러그인 jsdom에서 unavailable할 때 - 기본적으로 자신의 내부를 모킹하지 않도록 피하십시오: 커스텀 훅, 간단한 자식, 지역 유틸리티
테스트가 모든 어려운 부분을 가짜로 대체했을 때 통과한다면, 이는 당신에게 많은 릴리즈 신뢰를 얻은 것이 아니다.
runner API에 대한 예제 및 패턴을 원하는 팀에게, Capgo Jest 카테고리 테스트 메커니즘에 대해 아직 모르는 React 개발자들을 온보딩하는 경우, 특히 실용적인 참조 라이브러리입니다.
동기 테스트는 시간이 모호할 때 실패합니다.
동기 실패는 일반적으로 다음 중 하나의 오류 중 하나에서 발생합니다:
- 테스트가 너무 일찍 확인합니다.
- 테스트가 임의의 타이머로 대기합니다.
- 컴포넌트가 여러 번 업데이트되지만 테스트는 단일 전환만 모델링합니다.
안정적인 비동기 테스트는 일반적으로 다음 형태를 가집니다:
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 일부 요소의 görün상이 관심 있는 이벤트 인 경우 사용합니다. waitFor 조건이 더 광범위하거나 상태가 단일 쿼리로 표현되지 않는 경우 사용합니다. setTimeout 테스트에서 사용하지 마십시오. 타이머 동작을 명시적으로 테스트하고 가짜 타이머를 사용하는 경우에만 사용합니다.
React의 테스트 생태계는 업데이트에 대한 의미를 존중하는 것을 기대합니다. act() 업데이트가 플러시되는 시점을 생각해야 합니다.
어떤 모킹 도구를 사용해야 할지 알아보십시오.
다양한 모킹 도구는 서로 다른 문제를 해결합니다.
| 도구 | 최선의 사용 방법 | 일반적인 실수 |
|---|---|---|
jest.fn() | 독립된 가짜 콜백 함수 또는 주입 함수 | 단순한 콜백이면 모듈 전체를 대체하는 경우 |
jest.spyOn() | 실제 객체 또는 모듈의 한 메서드를 관찰하거나 오버라이드하는 경우 | 원래 구현을 복원하지 않는 경우 |
jest.mock() | import 경계에서 모듈 의존성을 교체하는 경우 | 기본적으로 대량의 모듈을 모킹하고 의미 있는 동작을 잃는 경우 |
예제 도움말:
- 필요할 때
jest.fn()컴포넌트가onSubmit속성을 받을 때. - 사용할 때, __CAPGO_KEEP_0__를 확인하는 데 필요할 때
jest.spyOn(), 저장 방법을 확인하거나 __CAPGO_KEEP_0__를 내보내는 호출을 사용할 때console.error사용할 때, 모듈을 가져올 때 I/O, native API, 또는 단위 경계 외부의 동작을 피하기 위해 - 오류 경로 테스트는 현대 React에서 많은 가이드가 부족한 영역입니다. 오류 경계, 지연된 상태 변경, 비동기 fallback UI는 'happy path' 클릭 예제만 테스트하는 것이 아니라, 첫 번째 클래스 테스트가 필요합니다. 자식이 예외를 발생시키면 fallback UI를 확인하세요. 요청이 실패하면 표시되는 회복 상태를 확인하세요. 로딩 중인 버튼이 비활성화되면 그 것도 확인하세요. 사용자가 기억하는 버그입니다.
jest.mock()when importing a module would otherwise hit I/O, native code, or behavior outside the unit boundary.
많은 팀이 여전히 보장과 같은 것으로 보장과 같은 보장을 추구하고 있습니다.
보장은 목표가 아닙니다.
보장은 목표가 아닙니다.
보장은 목표가 아닙니다.

보장은 목표가 아닙니다.
보장은 목표가 아닙니다.
개발자들을 트리비아 래퍼, 정적 마크업, 또는 한 줄의 통과 파일만으로 테스트하도록 밀어내면 유용하지 않습니다. 커버리지가 발견 도구로 다루어질 때만 유용합니다. 인증 상태, 청구 액션, 기능 플래그, 또는 업데이트 프롬프트가 테스트되지 않은 경우 그 신호입니다. 사용자 눈에 띄는 아이콘 컴포넌트가 테스트되지 않은 경우 일반적으로 그렇지 않습니다.
건강한 리뷰 질문은 간단합니다: 이 테스트가 릴리즈 위험을 줄이는가?
- 네: 사용자 눈에 띄는 비즈니스 로직을 검증합니다.
- 아마도: 비즈니스 로직이 쉽게 깨질 수 있는 부분을 보호합니다.
- 아니오: implementation 세부 사항을 주장하거나 다른 테스트의 가치를 중복하는 테스트입니다.
어떤 단위 테스트를 하지 않는다
React에 대한 많은 가이드가 여전히 omission에 충분한 시간을 보내지 않습니다. 그 틈새가 중요합니다. because over-mocking과 implementation-detail 테스트는 brittle한 스위트를 만들 수 있습니다. 사용자 경험은 여전히 깨지지만, BrowserStack의 React에 대한 what not to unit test의 가이드에서 이 패턴을 생략하거나 심하게 제한하세요: .
over-mocking과 implementation-detail 테스트는 brittle한 스위트를 만들 수 있습니다. 사용자 경험은 여전히 깨지지만, BrowserStack의 React에 대한 what not to unit test의 가이드에서
- 내부 상태 확인: 직접 테스트하지 말고, 패널이 열렸는지 테스트할 수 있는지 확인하세요.
isOpen프레임워크 동작: - 리액트가 효과를 호출했는지 테스트하지 말고, 효과가 변경한 결과를 테스트하세요. 세 번째-party 라이브러리 내부:
- 날짜 선택기나 라우터와 통합 테스트하세요, 라이브러리의 렌더링 로직이 아닌. 오버 브레이크된 단위:
- 모든 자식과 헬퍼를 모킹했다면, 더 이상 의미 있는 동작을 테스트하지 못할 수 있습니다. 나쁜 테스트는 더 나쁜 테스트입니다. 리팩토링을 막고 여전히 프로덕션 버그를 잡지 못하는 테스트입니다.
유용한 가이드라인은 경계 소유입니다. __CAPGO_KEEP_0__이 소유한 것을 테스트하세요. 리액트, 브라우저, 또는 성숙한 라이브러리가 이미 소유한 것을 테스트하지 말고, 통합層이 계약을 변경하지 않는 한.
A useful heuristic is boundary ownership. Test what your code owns. Don’t test what React, the browser, or a mature library already owns unless your integration layer changes the contract.
Don’t test what you don’t own unless your integration layer changes the contract.
Snapshots은 쓸모가 없다. 그저 잘못 사용하기 쉽다.
이용할 때는 컴포넌트가 안정적이고 단순한 출력을 가질 때, 넓은 구조적 diff가 의미가 있는 경우에만 사용하라. 인터랙티브나 동적이면서도 고도의 컴포넌트에서는 noise가 된다. 개발자들은 그들을 읽지 않고 그들을 자동으로 업데이트한다.
일반적으로 더 좋은 대안이 있다:
- 조건부 렌더링의 경우, key text의 존재 또는 비존재를 명확히 하라.
- 시각적 상태 변경의 경우, 중요하다고 여기는 역할, 레이블 또는 속성을 명확히 하라.
- 오류 및 대체의 경우, 실제 메시지 또는 경고 영역을 명확히 하라.
팀이 단위 테스트 외에 더 광범위한 품질 프로세스를 필요로 한다면, 테스트, 릴리스 체크, 롤백 계획을 하나의 시스템으로 다루는 애플리케이션 품질 보증 워크플로우 가 좋은 동반자이다. 테스트 품질을 빠르게 향상시키는 가장 큰 인식 전환은 테스트 품질을 향상시키는 것이 아니다. 테스트가 몇 개 있는지 물어보지 말고, 아직도 사용자에게 도달할 수 있는 실패가 있는지 물어보라.
다중 플랫폼 CI/CD PIPELINE에 테스트 통합하기
개발자 데스크톱에서만 테스트를 실행하는 테스트 스위트는 제안일 뿐이다.
테스트 스위트가 작동하려면, 모든 pull request가 같은 체크를 실행하고, clean 환경에서 실패할 때 merge를 차단해야 한다. 그게 명백한 소리지만, 많은 팀이 여전히 중요한 결함을 남겨두고 있다. 테스트는 수동으로 실행된다. 커버리지 보고서는 선택사항이다. 패키징 및 릴리스 작업은 테스트 작업이 완료되기 전에 시작된다. 그게 작은 UI regressions가 더 큰 릴리스 실패로 이어지는 이유다.

Pull Request 는 항상 동일한 게이트를 트리거해야 합니다.
React 단위 테스트를 위해 CI 에는 몇 가지 필수 요소가 필요합니다: 안전망으로 작동하도록 React 를 사용하십시오.
- Pull Request 에서 항상 실행하십시오.
- lockfile 에서 의존성을 설치하십시오.
- Pull Request 에서 항상 동일한 테스트 명령어를 사용하십시오.
- 테스트 실패 시 빠르게 실패하십시오.
- 테스트가 통과한 후에만 artifact 를 배포하십시오.
앱 팀의 Continuous Deployment릴리즈 전에 자신감을 쌓아야 합니다, 아니라 릴리즈 후에.
많은 팀에겐 간단한 GitHub Actions workflow 만 필요합니다.
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
This isn’t fancy, and that’s the point. The strongest pipelines are usually the least surprising ones.
Capacitor와 Electron에 대한 이유
브라우저 전용 앱보다 크로스 플랫폼 React 앱이 더 많은 릴리스 위험을 가지고 있습니다. 왜냐하면 동일한 UI code가 종종 다른 컨테이너에 다른 런타임 가정과 함께 배포되기 때문입니다.
몇 가지 예시를 통해 pipeline이 도움이 되는지 보여드리겠습니다.
- Capacitor 앱: 웹 code이 로컬에서 통과하지만 플러그인 브리지, 오프라인 상태, 또는 앱 라이프 사이클의 경계 사례가 패키징 후에 동작을 변경할 때 실패할 수 있습니다.
- Electron 앱: 렌더러 컴포넌트가 로드 프리로드 API, 윈도우 메시징, 또는 데스크톱 전용 상태에 의존할 수 있습니다. 이는 평범한 브라우저 테스트에서 존재하지 않기 때문에 의도적으로 모킹되지 않는 경우입니다.
- 공유 릴리스 트레인: 하나의 잘못된 배포본이 여러 대상에 영향을 미칠 수 있습니다. 만약 배포 프로세스가 엄격하게 배포를 게이트하지 않는다면.
이것이 단위 테스트가 패키징 작업 전에 실행되어야 하고, 패키징 작업이 배포 작업 전에 실행되어야 하는 이유입니다. 각 단계는 위험을 줄입니다. 단위 테스트는 로컬 회귀를 빠르게 잡을 수 있습니다. 플랫폼 패키징은 환경 가정의 검증을 수행합니다. 마지막으로 릴리스에 대한 신뢰를 확보하기 위해 수동 승인 또는 스테이지드 롤아웃을 사용합니다.
실용적인 GitHub Actions 워크플로
A 더 성숙한 pipeline은 일반적으로 책임을 나눈다:
- 테스트 작업: 빠른 단위 및 hook 테스트
- 빌드 작업: 테스트가 통과한 후만 프로덕션 빌드
- 패키지 작업: Capacitor 동기화, Electron 패키징 또는 아티팩트 번들링
- 릴리스 작업: 만약에 승인된 branch 또는 tag에서만 릴리스
Capacitor 또는 Electron 앱에 실시간 업데이트를 제공하는 팀에게는 이 릴리스 도구가 중요합니다. 그 workflow에서 하나의 옵션은 Capgo, 이 CapacitorJS 및 Electron 앱에 서명된 웹 번들을 릴리스하고 rollback 지원 및 채널 기반 롤아웃 제어를 제공합니다. 실제로, 그 의미는 React 테스트 작업이 웹 번들이 스테이징 또는 프로덕션 배포로 승격되기 전에 첫 번째 하드 게이트 역할을 할 수 있다는 것입니다.
The operational rule is straightforward. Don’t let release infrastructure compensate for weak tests. Use release infrastructure after reliable tests have already filtered out bad changes.
A dependable testing system changes team behavior. Engineers merge with less hesitation. Reviewers focus on edge cases instead of re-running basics manually. Release managers stop treating every deploy like a gamble. That’s the outcome of doing unit testing React well.
If your team ships React through Capacitor or Electron, release safety depends on more than green local tests. Capgo gives teams a controlled way to publish signed web updates, target rollout channels, and roll back bad bundles without waiting on store review, which fits naturally behind a CI pipeline that already requires passing unit tests before deployment.