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

예측 가능한 러너와 환경에서 시작하세요
모던 React 앱을 위한 기본 설정은 특히 Vite로 생성된 경우에:
- 테스트 러너: Jest는 특히 더 오래된 React 코드베이스와 기업 CI 스택에서 여전히 일반적입니다.
- 브라우저와 같은 환경:
jsdom컴포넌트 테스트가 DOM 출력을 렌더링할 수 있도록 합니다. - 테스트 라이브러리 유틸리티:
@testing-library/react그리고@testing-library/jest-dom - 단일 설정 엔트리 포인트: 일련의 매처와 글로벌 모킹을 등록하는 파일
React 테스트 지침이 강조하는 주요 워크플로는 간단합니다: jsdom-backed 환경에서 컴포넌트를 렌더링하고, 선택자와 같은 UI에 쿼리하여 DOM 변경을 확인합니다. getByText 또는 getByRole트리거 인터랙션, DOM 변경을 확인하고, assert합니다. 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을 사용한다면 괜찮습니다. 변환기라는 점이 중요한 것이 아니며, 일관성입니다. 하나의 경로를 선택하고 리포지토리에서 표준화하세요. 더 광범위한 자바스크립트 테스트 규칙에 대한 유용한 동료 참조로 Capgo의 자바스크립트 단위 테스트 가이드 팀 간 전달 문서입니다.
설정 파일을 추가하세요. 스위트가 의존하는
적절한 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가 더 엄격한 구성으로 실행하면 merge 후에 놀라운 오류가 발생합니다. 스크립트를 명확하게 유지하세요:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand --coverage"
}
}
새 팀원들이 동일한 기준을 빠르게 얻을 수 있도록 도와주는 짧은_walkthrough가 있습니다:
가장 영향력 있는 설정 선택은 기본값에 대한 discipline입니다. alias를 config에 넣고 환경 모킹을 하나의 설정 파일에 넣고 사용하세요 jsdom UI 테스트와 가능할 때 더 가벼운 환경을 사용하여 pure utility를 위해
테스트가 여전히 6개월 후에도 의미가 있는지에 대한 문제는 조직이 테스트를 작성하는 문제가 아닙니다.
React 컴포넌트의 단위 테스트를 위한 표준 패턴은 여전히 올바른 패턴입니다:
컴포넌트를 렌더링하고 사용자 중심의 선택자로 UI를 쿼리하고 상호 작용을触发하고 결과적인 DOM 변경을 확인하는 것입니다. 이 패턴은 implementation details와 관련된 상태나 props와 같은 테스트를 멀리하는 데 도움이 됩니다. React 테스트 가이드에서 설명한 것과 같습니다.테스트가 여전히 6개월 후에도 의미가 있는지에 대한 문제는 조직이 테스트를 작성하는 문제가 아닙니다. React 컴포넌트의 단위 테스트를 위한 표준 패턴은 여전히 올바른 패턴입니다: . 그 패턴을 제한된 방식으로 적용하는 것이 중요합니다.
사용자가 액세스하는 것처럼 액세스 소스 테스트
기본적인 Accordion 컴포넌트를 선택하세요. 버튼과 제목이 렌더링됩니다. 패널 콘텐츠는 숨겨져 있습니다. 버튼을 클릭하면 콘텐츠가 표시되고 접근성 상태가 업데이트됩니다.
이러한 유용한 테스트를 위한 충분한 동작입니다:
- 초기 렌더링은 제목만 표시하지만 콘텐츠는 표시하지 않습니다.
- 트리거를 클릭하면 콘텐츠가 표시됩니다.
- 클릭하면 다시 접어집니다.
- 접근성 속성이 표시 상태를 반영합니다.
마지막 점은 자주 생략됩니다. 컴포넌트가 aria-expanded, aria-controls또는 역할 기반 구조를 사용한다면, 그들을 확인하세요. 그것들은 구현 세부 사항이 아닙니다. 사용자와의 계약의 일부입니다.
최선의 컴포넌트 테스트는 받고 싶지 않은 버그 보고서와 같습니다.
선택된 쿼리
React Testing Library는 여러 가지 쿼리 스타일을 제공하지만, 그들 사이에는 상호 교환할 수 있는 것은 아니다. 잘못된 선택은 테스트를 불필요하게 또는 오해를 일으키게 할 수 있다.
| 쿼리 종류 | 요소가 발견될 때 | 요소가 발견되지 않을 때 | 사용 사례 예시 |
|---|---|---|---|
getBy |
요소가 즉시 반환된다. | 에러를 즉시 발생시킨다. | 화면에 이미 존재해야 하는 버튼이나 헤딩을 확인한다. |
queryBy |
요소가 즉시 반환된다. | 반환 null |
화면에 존재하지 않아야 하는 숨겨진 콘텐츠를 확인한다. |
findBy |
요소가 나타날 때 해결 | 대기 후에 거부 | fetch 또는 지연 업데이트 후에 로드된 콘텐츠가 나타날 때 확인 |
간단한 정신 모델이 도움이 됩니다:
- 이미 존재해야 하는 것에 대해 사용
getBy아직 존재하지 않아야 하는 것에 대해 사용 - UI가 나중에 변경될 때 사용
queryBy테스트가 시작될 때 - for 모든 것에 대해 시작되면, 일반적으로 작성자가 컴포넌트가 업데이트될 때 확신하지 못한다는 것을 의미합니다. 그 불확실성이 나중에 불안정성으로 변합니다.
findBy__CAPGO_KEEP_0__
__CAPGO_KEEP_1__ findBy __CAPGO_KEEP_2__
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');
});
이것은 중요한 부분입니다. 내부 상태에 대한 주장이나, 호출된 것을 확인하는 테스트, 렌더링 tree의 스냅샷이 없습니다. 이러한 테스트는 유지보수에만 도움이 되며, 신뢰를 높이지는 않습니다. setOpen 컴포넌트 테스트를 강화하는 몇 가지 습관이 있습니다:
역할 기반 쿼리 사용하기:
- 버튼, 헤더, 대화상자, 경고, 입력은 일반적으로 역할로 찾습니다. 테스트를 좁게 유지하기:
- 사용자에게 보이는 하나의 동작을 테스트하는 하나의 테스트로 유지하면, 실패를 읽기 쉽게 유지할 수 있습니다. 테스트 이름을 결과에 맞추기:
- “aria-expanded 업데이트 할 때 열림”은 “정확하게 작동한다”보다 훨씬 유용합니다. 역할 기반 쿼리 사용하기: 버튼, 헤더, 대화상자, 경고, 입력은 일반적으로 역할로 찾습니다.
DOM을 통해 컴포넌트를 테스트하는 것이 어려우면, 그 때가 디자인 문제를 드러내는 때가 많습니다. 상태를 잘못된 곳에 숨기고 있거나, 의미 있는 마크업을 부족한 경우가 많습니다. 좋은 테스트는 팀을 더 나은 컴포넌트로 이끄는 경우가 많습니다.
커스텀 훅스와 애플리케이션 로직 테스트
리액트 앱은 컴포넌트 밖에서 많은 중요한 동작을 숨깁니다. 상태 전환은 훅스에서 발생하고, 유효성 검사와 형식화는 도우미 함수에서 발생합니다. 데이터 조작은 렌더링되기 전에 발생합니다. 만약에만 표시되는 컴포넌트만 테스트한다면, 여전히 프로덕션 동작을 깨는 code를 놓치게 될 것입니다.
훅은 리액트에 대한 aware한 해arness가 필요합니다
커스텀 훅스는 리액트가 실행되도록 하려면, 리액트에 대해 테스트해야 합니다. renderHook 상태 변경을 호출하는 것을 wrap하는 것이 좋습니다. act().
작은 useToggle 훅의 테스트는 public contract에 집중해야 합니다.
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);
});
제품 팀이 재사용 가능한 UI나 기능 원형을 개발하는 경우, 이 패턴은 매우 중요합니다. 훅은 앱, 디자인 시스템, 또는 내부 도구 간에 공유되는 인터페이스가 됩니다. 만약에 상업 목적으로 재사용 가능한 동작을 설계한다면,
커스텀 훅스에 대한 리소스 hooks for makers' products 함수 테스트를 빠르고 명확하게 하기 위해 React-특정 도구를 사용하지 마세요.
순수 논리는 테스트에서 순수하게 유지하세요.
모든 것이 필요하지 않습니다. jsdomReact 또는 Testing Library가 필요하지 않다면, 함수가 순수하다면 Jest에서 Node 환경에서 테스트하세요.
예시:
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가 필요하지 않다면, 그 함수에 렌더링 tree를 제공하지 마세요. React-특정 도구는 오버헤드를 추가합니다. 비즈니스 로직 테스트는 작고 빠르게 유지하고, 테스트하려는 함수와 가까워야 합니다.
실용적인 분리는 잘 작동합니다:
- HOOKS: 사용하고, wrapper provider를 필요할 때 사용하세요.
renderHook,act()유틸리티: - 속도와 명확성을 위해 React-특정 도구를 사용하지 마세요. React 단위 테스트에서 DOM이 없는 Jest 사용.
- 상태 관리를 위한 교차 로직: 컴포넌트 테스트가 너무 많은 로직을 수행할 때 테스트 가능한 헬퍼 함수로 로직을 끌어올려라.
팀은 종종 컴포넌트 테스트에 로직을 포함시키는데, 이는 실제로 하위 스택에 속하는 로직이다. 로직을 분리하면 컴포넌트 테스트가 더 깨끗해지고 로직 테스트가 더 빠르다.
고급 기술을 마스터하라: 모킹과 비동기
대부분의 불안정한 React 테스트는 두 가지 장소에서 깨진다. 의존성 경계와 시간이다.
이것이 비동기 테스트와 모킹이 실제로 toy 테스트 스위트와 신뢰할 수 있는 테스트 스위트를 구분하는 이유이다. 한 분석에 따르면 46.5%의 테스트 불안정성은 환경 또는 자원 관련 문제로 인한 비동기 타이밍 때문이다. 이 React 단위 테스트 분석에서. React 앱에서 이것은 직접적으로 상태 전환, 지연 렌더링, 네트워크 기반 UI, 테스트가 기다리지 않고 추측하는 경우에 해당한다.Advanced React Testing Techniques에 대한 비교 차트, 특히 Mocking Dependencies versus Asynchronous Testing을 특정하게.

Mocking boundary, not every layer
가장 빠른 방법으로 잘못된 테스트를 작성하는 것은 컴포넌트 tree의 절반을 mock하고 나서 자신의 mock가 작동했는지 확인하는 것입니다.
계정 데이터를 가져오는 컴포넌트의 경우 네트워크 클라이언트 또는 API 모듈을 mock하세요. hook, 자식 row 컴포넌트, 로딩 스피너, 세 가지 유틸리티 함수를 mock하지 마세요. 테스트가 truly 필요로 하는 경우에만 격리할 수 있습니다.
이 규칙 세트를 사용하세요.
- 외부 서비스를 mock하세요: HTTP 클라이언트, 분석, 브라우저 전용 API, 네이티브 브리지를 mock하세요.
- 불안정한 플랫폼 API를 mock하세요:
matchMedia, 타이머, Electron preload 인터페이스, Capacitor 플러그인을 jsdom에서 unavailable할 때 - 기본적으로 자신의 내부를 mock하지 마세요: 커스텀 훅, 간단한 자식, 로컬 유틸리티
테스트가 모든 어려운 부분을 가짜로 대체하여 통과하면, 이는 당신에게 많은 릴리스 신뢰를 제공하지 않습니다.
runner API, Capgo에 대한 예시와 패턴을 제공하는 팀 테스트 튜토리얼 개발자가 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 원하는 요소의 나타남이 이벤트로 관심이 있는 경우 사용하세요. waitFor 조건이 더 넓거나 상태를 단일 쿼리로 표현할 수 없는 경우 사용하세요. 사용하지 마세요. setTimeout 테스트를 하기 전에 timer 동작을 명시적으로 테스트하고 가짜 타이머를 사용하지 않는 한
React의 테스트 생태계는 업데이트에 대한 의미를 존중하는 것을 기대합니다. act() 테스트 라이브러리는 많은 것을 처리하지만, 상태를 수동으로 조정하거나 타이머를 앞서가면 여전히 업데이트가 플러시되는 시점에 대해 생각해야 합니다.
어떤 모킹 도구를 사용해야 할지 알기
다양한 모킹 도구는 서로 다른 문제를 해결합니다:
| 도구 | 최적의 사용 | 일반적인 오류 |
|---|---|---|
jest.fn() |
독립적인 가짜 콜백 또는 주입 함수 | 단순한 콜백만으로 충분한 경우에 모듈 전체를 대체하는 것 |
jest.spyOn() |
실제 객체 또는 모듈의 한 메서드를 관찰하거나 오버라이드하는 것 | 원래 구현을 복원하지 않는 것 |
jest.mock() |
모듈 의존성을 임포트 경계에서 교체 | 대형 모듈을 기본적으로 모킹하고 의미 있는 동작을 잃는 경우 |
예시를 보려면:
- 필요할 때
jest.fn()컴포넌트가 프로퍼티를 받을 때onSubmit사용 - 저장 방법, 또는 하나의 __CAPGO_KEEP_0__ 호출을 내보내는 경우 동작을 검증할 때
jest.spyOn()사용console.error모듈을 임포트할 때 I/O, 네이티브 API, 또는 단위 경계를 넘어가는 동작을 피할 때 - 오류 경로 테스트
jest.mock()when importing a module would otherwise hit I/O, native code, or behavior outside the unit boundary.
오류 경로 테스트
테스트 품질과 전략을 개선하는 방법
많은 팀이 여전히 신뢰와 같은 것으로 보이지만 실제로는 아님을 알기 위해 커버리지에 집중하고 있습니다.
커버리지 목표를 달성할 수 있으면도 중요한 회귀 테스트를 놓칠 수 있습니다. 얕은 확인, 광범위한 스냅샷, 내부 모킹을 포함하는 테스트 스위트는 안전성을 나타내는 것처럼 보이면서 유지 보수 비용을 증가시킵니다.

커버리지 목표는 목표가 아닙니다.
커버리지 보고서는 다음 질문에 답할 때 유용합니다: 아직 보호되지 않은 중요한 경로가 무엇인가요?
개발자들을 테스트할 수 있는 불필요한 wrapper, 정적 마크업, 또는 한 줄의 통과 파일로 밀어내지 않도록 하세요. 커버리지 목표를 달성하기 위해 개발자들을 밀어내지 마세요. 커버리지 목표를 발견 도구로 사용하세요. 인증 상태, 청구 액션, 기능 플래그, 또는 업데이트 프롬프트가 테스트되지 않은 경우 신호입니다. 사용자에게 보이지 않는 아이콘 컴포넌트가 테스트되지 않은 경우 일반적으로 그렇지 않습니다.
건강한 검토 질문은 간단합니다: 이 테스트가 릴리스 위험을 줄이는가?
- 예: 사용자에게 보이는 동작을 검증하는 경로입니다.
- 아마도: 비즈니스 논리를 보호하는 경로입니다. 이 논리가 쉽게 깨질 수 있는 경우 리팩토링 중에 깨질 수 있습니다.
- No: 구현 세부 사항이나 다른 테스트의 값을 복제하는 것을 확인합니다.
어떤 것을 단위 테스트하지 말아야 할까요
React에 대한 많은 가이드가 여전히 생략에 충분한 시간을 할애하지 않습니다. 그 틈새는 중요합니다. 왜냐하면 과도한 모킹과 구현 세부 사항 테스트가 깨지지 않는 테스트 스위트를 만들 수 있지만 사용자 경험은 여전히 깨지기 때문입니다. BrowserStack의 React에서 테스트하지 말아야 할 것에 대한 가이드에서 React에서 테스트하지 말아야 할 것에 대한 가이드.
다음 패턴을 생략하거나 심하게 제한하세요:
- 내부 상태 확인: 직접 확인하지 말고, 패널이 열렸는지 테스트하세요.
isOpen프레임워크 동작: - React가 효과를 호출했는지 확인하지 말고, 효과가 변경한 결과를 테스트하세요. 세 번째-party 라이브러리 내부:
- 테스트하지 마세요. 통합 테스트는 데이트 픽커나 라우터와 같은 외부 라이브러리와의 통합을 테스트하는 데 사용됩니다.
- 오버 브레이크된 단위 테스트: 모든 자식과 헬퍼를 모킹했다면, 더 이상 의미 있는 동작을 테스트하지 못할 수 있습니다.
잘못된 테스트는 리팩토링을 막고 여전히 프로덕션 버그를 잡지 못하는 테스트보다 나쁩니다.
code가 소유한 경계의 소유권을 테스트하는 것이 유용한 지침입니다. React, 브라우저, 또는 성숙한 라이브러리가 이미 소유하고 있는 것을 테스트하지 마십시오. 단, 통합層이 계약을 변경한다면.
샷샷이 도움이 되는 곳과 도움이 되지 않는 곳
샷샷은 쓸모가 없습니다. 단순히 잘못 사용하기 쉽습니다.
샷샷은 구조적 차이의 의미가 있는 컴포넌트의 안정적인 단순한 출력을 위한 경우에만 사용하십시오. 반응형 또는 동적이지만 상호 작용이 많은 컴포넌트의 경우에는 소음이 됩니다. 개발자는 그들을 읽지 않고 반복적으로 업데이트하기 시작합니다.
보다 좋은 대안이 있습니다:
- 조건부 렌더링의 경우, 키 텍스트의 존재 또는 비존재를 확인하십시오.
- 시각적 상태 변경의 경우, 중요하는 역할, 레이블, 또는 속성을 확인하십시오.
- 오류 및 대체의 경우, 실제 메시지 또는 알림 영역을 확인하십시오.
만약 팀이 단위 테스트 외에도 더 광범위한 품질 프로세스를 필요로 한다면, 어플리케이션 품질 보증 워크플로우 테스트, 릴리즈 체크, 롤백 계획을 하나의 시스템으로 다루는 것이 품질을 빠르게 향상시키는 마음의 전환입니다.
테스트를 CI/CD PIPELINE에 통합하는 방법
개발자 노트북에서만 테스트를 실행하는 테스트 스위트는 제안일 뿐입니다.
테스트 스위트가 작동하려면, 모든 pull request가 동일한 환경에서 동일한 체크를 실행하고, 체크가 실패하면 merge를 차단해야 합니다.

테스트는 수동으로 실행됩니다.
커버리지 보고서가 선택 사항입니다.
- 패키징 및 릴리즈 작업은 테스트 작업이 완료되기 전에 시작됩니다.
- 이것은 작은 UI regressions가 더 큰 릴리즈 실패로 이어지는 것입니다.
- CI/CD PIPELINE에 React 자동화 테스트를 통합하는 5단계 프로세스 다이어그램입니다.
- 테스트 실패 시 빠르게 실패
- 테스트 통과 후만 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 앱: 렌더링 컴포넌트는 프리로드 API, 윈도우 메시징, 또는 데스크톱 전용 상태에 의존할 수 있습니다. 이들은 평범한 브라우저 테스트에서 존재하지 않기 때문에 의도적으로 모킹해야 합니다.
- 공유 릴리스 트레인: 하나의 잘못된 배포본이 여러 대상에 영향을 줄 수 있습니다. 배포 프로세스가 엄격하게 배포를 게이트하지 않으면서.
이것은 단위 테스트가 패키징 작업 전에 실행되어야 하며, 패키징 작업이 배포 작업 전에 실행되어야 한다는 것을 의미합니다. 각 단계는 위험을 줄입니다. 단위 테스트는 지역적인 회귀를 빠르게 잡을 수 있습니다. 플랫폼 패키징은 환경 가정의 검증을 수행합니다. 마지막 릴리스에 대한 신뢰를 처리하는 것은 수동 승인 또는 스테이지 롤아웃입니다.
실용적인 GitHub Actions 워크플로우
더 성숙한 PIPELINE은 책임을 나누어줍니다:
- 테스트 작업: 빠른 단위 및 훅 테스트
- 빌드 작업: 테스트가 통과한 후에만 프로덕션 빌드
- 패키징 작업: Capacitor sync, Electron 패키징, 또는 아티팩트 번들링
- 릴리스 잡: 만약에 승인된 branch 또는 tag에서만 릴리스를 수행한다
리액트 앱을 실시간으로 업데이트하는 팀이 Capacitor 또는 Electron 앱을 배포하는 경우, 릴리스 도구가 중요한데, 그 중 하나는 CapgoCapacitorJS 및 Electron 앱에 대한 서명된 웹 번들을 배포하고 롤백 지원 및 채널 기반 롤아웃 제어를 제공하는 옵션입니다. 실제로, 그 의미는 React 테스트 잡이 스테이징 또는 프로덕션 배포로 승격되기 전에 웹 번들을 최초로 강제로 통과시키는 것입니다.
작업 절차는 간단합니다. 릴리스 인프라가 약한 테스트를 보상하지 않도록 하세요. 신뢰할 수 있는 테스트 시스템이 있으면, 릴리스 인프라를 사용하기 전에 이미 테스트에서 문제가 있는 변경 사항을 필터링하세요.
신뢰할 수 있는 테스트 시스템은 팀의 행동을 바꿉니다. 엔지니어들은 더 많은 주의 없이 머지합니다. 리뷰어들은 에지 케이스에 집중하여 기본적인 것을 수동으로 다시 실행하지 않습니다. 릴리스 매니저들은 배포를 gamble로 다루지 않습니다. 그게 리액트를 잘 테스트하는 결과입니다.
리액트를 Capacitor 또는 Electron을 통해 배포하는 팀은, 릴리스 안전성이 단지 로컬 테스트가 green인 경우에만 의존할 수 없습니다. Capgo 팀에게 서명된 웹 업데이트, 롤아웃 채널을 지정하고, 문제가 있는 번들을 롤백할 수 있는 제어를 제공하는 안전한 방법을 제공합니다. 스토어 리뷰를 기다리지 않고, CI pipeline이 이미 배포 전에 단위 테스트를 통과시키도록 요구하는 자연스러운 위치에 있습니다.