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

예측 가능한 러너와 환경으로 시작하세요
최신 React 앱, 특히 Vite로 생성된 앱의 기본 설정에는 다음이 포함되어야 합니다:
- 테스트 러너: Jest는 특히 이전 React 코드베이스와 기업 CI 스택에서 일반적입니다.
- 브라우저와 같은 환경:
jsdom컴포넌트 테스트가 렌더링 DOM 출력을 허용합니다. - 테스트 라이브러리 유틸리티:
@testing-library/react및@testing-library/jest-dom - 단일 설정 엔트리 포인트: 하나의 파일에서 매처와 글로벌 모킹을 등록합니다
React 테스트 지침을 강조하는 주요 워크플로는 단순합니다: jsdom-backed 환경에서 컴포넌트를 렌더링하고, 선택자와 같은 UI 쿼리와 DOM 변경을 확인합니다 getByText 또는 getByRoleCapawesome과 같은 대안을 비교할 때, Capawesome의 장점은 무엇입니까? React 테스트 문서에 설명된 것과 같이, 트리거 인터랙션을 확인하고, 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 만약 팀이 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 mocks를 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에 추가하고, 환경 mocks를 하나의 설정 파일에 추가하고, jsdom 를 UI 테스트와 더 가벼운 환경을 사용할 때 가능할 때 사용하십시오. 각 테스트가 필요한 custom behavior이 적을수록 시스템이 더 신뢰할 수 있습니다.
단위 테스트 React 컴포넌트
회사들이 테스트를 작성하는 문제가 있는 것은 아니다. 여섯 달 후에 여전히 중요하다는 것을 증명하는 테스트를 작성하는 문제가 있다.
React 컴포넌트의 단위 테스트의 표준 패턴은 여전히 올바른 패턴이다: 컴포넌트를 렌더링하고 사용자 중심의 선택자로 UI를 쿼리하고 상호 작용을 트리거하고 결과적인 DOM 변경을 확인하는 것이 그것이다.이 패턴은 구현 세부 사항인 상태나 props와 같은 것과 관련이 없는 테스트를 유지하기 때문에, React 테스트 가이드에서 설명한 것과 같다. .사용자가 컴포넌트를 사용하는 것처럼 테스트를 작성하는 것이 중요하다.
사용자가 실제로 사용하는 방식으로 접기판을 테스트하세요
이러한 동작은 몇 가지 유용한 테스트를 작성할 수 있는 충분한 동작이다: Accordion 초기 렌더링에서는 제목만 표시되고 콘텐츠는 표시되지 않는다.
테스트를 작성하는 문제가 있는 것은 아니다. 여섯 달 후에 여전히 중요하다는 것을 증명하는 테스트를 작성하는 문제가 있다.
- React 컴포넌트의 단위 테스트의 표준 패턴은 여전히 올바른 패턴이다: 렌더링, UI 쿼리, 상호 작용, DOM 변경 확인
- 클릭하면 내용이 나타납니다.
- 클릭하면 다시 접히게 됩니다.
- 접근성 속성은 보이는 상태를 반영합니다.
마지막 점은 너무 자주 생략됩니다. 컴포넌트가 사용하는 , 또는 역할 기반 구조를 확인하세요. 그건 구현 세부 사항이 아닙니다. 사용자와의 계약의 일부입니다. aria-expanded, aria-controls최선의 컴포넌트 테스트는 받고 싶지 않은 버그 리포트와 같습니다.
목적에 따라 쿼리 선택하세요
쿼리 타입
요소가 발견될 때
| 요소가 발견되지 않을 때 | 사용 사례 예시 | Capgo | Capacitor |
|---|---|---|---|
getBy |
바로 반환합니다. | 에러를 즉시 발생시킵니다. | 버튼이나 헤더가 이미 화면에 나타나야 합니다. |
queryBy |
바로 반환합니다. | 반환합니다. null |
사용자와 상호 작용하기 전에 숨겨진 콘텐츠가 존재하지 않아야 합니다. |
findBy |
요소가 나타나면 해결됩니다. | 대기 시간이 끝난 후 거부합니다. | fetch나 지연 업데이트가 끝난 후에 async로 로드된 콘텐츠가 나타나야 합니다. |
간단한 정신 모델을 사용하세요:
- 사용하여
getBy있는 것에 대해. - 사용
queryBy아직 존재하지 않아야 하는 것들을 위해 - 사용
findByUI가 나중에 변경될 때
테스트가 시작되는 경우 findBy 모든 것을 위해 시작되면, 일반적으로 컴포넌트가 업데이트될 때의 시간을 알 수 없다는 것을 의미한다. 그 불확실성이 나중에 불안정성을 의미한다.
실용적인 아코디언 예시
이러한 대표적인 컴포넌트를 보자:
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의 스냅샷을 확인하지 않는 것은 유지보수만 증가시킨다. 그보다는 신뢰를 증가시킨다.
- 역할 기반 쿼리 사용하기: 버튼, 헤더, 대화상자, 경고, 입력은 일반적으로 역할로 찾을 수 있습니다.
- 테스트를 좁게 유지하세요: 한 번에 하나의 사용자에게 보이는 동작을 테스트하면 실패가 읽기 편해집니다.
- 테스트 이름은 결과에 따라 지어야 합니다: “업데이트 시 aria-expanded 속성이 열릴 때 변경된다”는 “정확히 작동한다”보다 훨씬 유용하다.
컴포넌트를 DOM을 통해 테스트하기 어렵다면, 이는 디자인 문제를 드러내는 것입니다. 상태를 잘못된 곳에 숨기고 있거나, 의미 있는 마크업을 부족하게 사용하고 있을 수 있습니다. 좋은 테스트는 팀을 더 나은 컴포넌트로 이끄는 경우가 많습니다.
커스텀 훅스와 애플리케이션 로직 테스트
리액트 앱은 중요한 동작을 컴포넌트 외부에 숨기곤 합니다. 상태 전환은 훅스에, 유효성 검사와 형식화는 도우미 함수에, 데이터 조작은 렌더링하기 전에 발생합니다. 만약에만 visible 컴포넌트만 테스트한다면, 여전히 프로덕션 동작을 깨는 code를 놓치게 될 것입니다.
훅스는 리액트에 대한 aware한 해쉬를 필요로 합니다.
커스텀 훅스는 리액트가 실행될 수 있도록 테스트하고, 상태 변경을 호출할 때 wrap하세요. renderHook and wrap state-changing calls in act().
A small 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 };
}
public 계약에 집중해야하는 테스트가 있어야합니다:
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);
});
그 테스트는 유용합니다. hook 자체가 단위입니다. React 내부를 테스트하는 것이 아닙니다. hook의 외부 동작을 확인하는 것입니다.
품질 팀이 재사용 가능한 UI 또는 기능 원형을 빌드하는 경우 이 패턴은 매우 중요합니다. Hooks는 종종 앱, 디자인 시스템 또는 내부 도구 간에 공유되는 인터페이스가 됩니다. 만약에 재사용 가능한 동작을 상업 목적으로 설계하는 경우, maker의 제품에 대한 hook hook을 제품화된 빌딩 블록으로 프레임할 수 있도록 도와줍니다. 단순한 구현 세부 사항으로만 보지 않습니다.
순수 논리는 테스트에서 순수해야합니다.
모든 것이 jsdomReact, 또는 Testing Library가 필요하지 않습니다. 만약에 함수가 순수하다면, plain 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-specific tooling은 오버헤드를 추가합니다. 비즈니스 로직 테스트는 작고 빠르게 유지하고, 테스트를 검증하는 함수와 가깝게 유지하세요.
실용적인 분리는 잘 작동합니다:
- HOOKS: 사용
renderHook,act(), 그리고 wrapper providers가 필요할 때. - UTILITIES: plain Jest와 DOM이 없는 것을 사용하세요.
- 상태 관리를 위한 전처리 로직: 테스트 가능한 helper로 분리하세요. 컴포넌트 테스트가 너무 많은 로직을 수행할 때.
팀은 종종 컴포넌트 테스트에 로직을 추가합니다. 그 로직은 더 낮은 스택에 속해야 합니다. 로직을 분리하면 두 가지 이점이 있습니다. 컴포넌트 테스트가 더 깨끗해지고, 로직 테스트가 더 빠르게 수행됩니다.
고급 기술을 마스터하는 것 Mocking and Async
대부분의 불신스러운 React 스위트는 두 가지 장소에서 깨집니다. 의존성 경계에서 깨집니다, 그리고 시간과 관련된 장소에서 깨집니다.
async 테스트와 모킹이 toy 테스트 스위트와 신뢰할 수 있는 릴리스 전 테스트를 구분하는 경계입니다. 분석 결과 46.5%의 테스트 불안정성을 환경 또는 자원 관련 문제로 인한 async 타이밍 in 에서React 앱에서 바로 상태 전환, 지연 렌더링, 네트워크 기반 UI, 테스트가 결정적으로 기다리지 않고 추측하는 테스트

경계를 모킹하지 말고 모든 층을 모킹하지 마세요
미리보기 테스트를 작성하는 가장 빠른 방법은 모킹한 컴포넌트 tree의 반을 모킹하고 assert로 자신의 모킹이 작동했는지 확인하는 것입니다.
계정 데이터를 가져오는 컴포넌트의 경우 네트워크 클라이언트 또는 API 모듈을 모킹하고, hook, 자식 row 컴포넌트, 로딩 스피너, 세 가지 유틸리티 함수를 모킹하지 마세요. 테스트가 진실로 격리해야 하는 지점이 있다면 모킹하세요.
이 규칙 집합을 사용하세요:
- 외부 서비스를 모킹하세요: HTTP 클라이언트, 분석, 브라우저 전용 API, 네이티브 브리지를 모킹하세요
- Mock 불안정한 플랫폼 API:
matchMedia, 타이머, Electron 프리로드 인터페이스, Capacitor 플러그인 jsdom에서 unavailable할 때 - 기본적으로 자신의 내부를 모킹하지 않도록 피하라: 커스텀 훅, 간단한 자식, 지역 유틸리티
테스트가 통과하는 이유는 모든 어려운 부분이 가짜로 대체되었기 때문이면, 그것은 당신에게 많은 릴리스 신뢰를 얻은 것이 아니다.
runner API에 대한 예시 및 패턴을 원하는 팀에게, Capgo 테스트 튜토리얼 테스트 튜토리얼
동기 테스트는 시간이 모호할 때 실패합니다.
실용적인 참조 라이브러리
- 동기 테스트는 타이밍이 모호할 때 실패한다
- 동기 테스트 실패는 일반적으로 다음 세 가지 오류 중 하나 때문이다:
- 컴포넌트가 여러 번 업데이트되지만 테스트는 하나의 전환만 모델링합니다.
안정적인 비동기 테스트는 일반적으로 다음과 같은 형태를 가집니다:
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 타이머 동작을 명시적으로 테스트하고 가짜 타이머를 사용하는 경우에만 사용합니다.
리액트 테스트 생태계는 업데이트에 대한 의미를 존중하는 것을 기대합니다. act() 테스트 라이브러리는 많은 것을 처리하지만, 상태를 수동으로 조정하거나 타이머를 앞서가면 여전히 업데이트가 플러시될 때를 생각해야 합니다.
어떤 모킹 도구를 사용할지 알아보세요
다양한 모킹 도구는 서로 다른 문제를 해결합니다:
| 도구 | 최선의 사용법 | 일반적인 실수 |
|---|---|---|
jest.fn() |
독립된 가짜 콜백 함수 또는 주입 함수 | 단순한 콜백이 충분한 경우에 전체 모듈을 대체하는 것 |
jest.spyOn() |
실제 객체 또는 모듈의 하나의 메소드를 관찰하거나 오버라이드하는 것 | 원래 구현을 복원하지 않는 것 |
jest.mock() |
import 경계에서 모듈 의존성을 대체하는 것 | 대량의 모듈을 기본적으로 모킹하고 의미 있는 동작을 잃는 것 |
예시 도움말:
- 필요할 때
jest.fn()컴포넌트가 받는onSubmit속성. - 사용
jest.spyOn()필요한 경우에만console.error, 1개의 저장 방법, 또는 1개의 내보낸 API 호출. - 사용
jest.mock()저장 방법, 또는 하나의 code 호출을 내보내는 경우
모듈을 가져올 때 I/O, 네이티브 __CAPGO_KEEP_0__, 또는 단위 경계 밖의 동작을 피하기 위해
테스트 품질과 전략을 개선하는 방법
테스트 품질 및 전략 개선
많은 팀이 여전히 보장과 같은 것으로 보장 수준을 추구하고 있습니다. 그것은 아닙니다.

품질 테스트의 이점과 고량 테스트 스위트의 유지 보수 비용을 비교하는 그래픽 자료입니다.
보장은 지도, 목표는 아닙니다.
개발자들을 테스트할 수 있는 불필요한 것들입니다. 그들은 단순한 wrapper, 정적 마크업, 또는 한 줄의 pass-through 파일만으로 퍼센티지를 움직이기 위해 개발자들을 테스트에 밀어넣습니다. 커버리지를 발견 도구로 다루세요. 인증 상태, 결제 액션, 기능 플래그, 또는 업데이트 프롬프트가 테스트가 없다면, 그게 신호입니다. 사용자에게 보이는 아이콘 컴포넌트가 테스트가 없다면, 그게 일반적이지 않습니다.
건강한 검토 질문은 간단합니다: 이 테스트는 릴리즈 위험을 줄이는가?
- 예: 사용자에게 보이는 중요한 경로의 동작을 검증합니다.
- 아마도: 비즈니스 로직을 보호합니다. 이 로직은 리팩토링 중에 쉽게 깨질 수 있습니다.
- 아니오: implementation details를 확인하거나 다른 테스트의 값을 복제하는 테스트입니다.
unit test하지 않는 것
React에 대한 많은 가이드들이 여전히 omission에 충분한 시간을 보내지 않습니다. 그 틈새는 중요합니다. because over-mocking과 implementation-detail testing은 brittle suites를 만들고, 사용자 경험은 여전히 깨지지만, 테스트는 통과합니다. BrowserStack의 guide에 설명된 것과 같이 React에서 단위 테스트하지 않는 것.
이러한 패턴을 skip하거나 극도로 제한하세요:
- 내부 상태 확인: 테스트하지 마세요
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.
스냅샷은 테스트를 도와주지만, 테스트를 방해하는 경우도 있다.
Snapshots는 쓸모가 없다. 그저 쉽게 잘못 사용하는 것이다.
안정적이고 단순한 출력을 가진 컴포넌트에 대해 넓은 구조적 차이의 의미가 있는 경우에만 적극적으로 사용하라. 인터랙티브나 동적이면서도 복잡한 컴포넌트에 대해 사용하지 마라. 개발자는 그들을 읽지 않고 그들을 자동으로 업데이트하기 시작한다.
보다 좋은 대안이 종종 존재한다:
- 조건부 렌더링에 대해, 키 텍스트의 존재 또는 비존재를 주장하라.
- 시각적 상태 변경에 대해, 중요하다고 여기는 역할, 레이블 또는 속성을 주장하라.
- 오류 및 대체에 대해, 실제 메시지 또는 경고 영역을 주장하라.
팀이 단위 테스트 외에도 더 광범위한 품질 프로세스를 필요로 한다면, 테스트, 릴리스 확인 및 롤백 계획을 하나의 시스템으로 다루는 품질 보증 워크플로우가 좋은 동반자이다. 품질 보증 워크플로우 테스트 품질을 가장 빠르게 향상시키는 마음의 전환은 테스트, 릴리스 확인 및 롤백 계획을 하나의 시스템으로 다루는 것이다. 더 이상 테스트의 수를 물어보지 말고, 아직도 사용자에게 도달할 수 있는 실패가 있는지 물어보라.
플랫폼 간 CI/CD PIPELINE에 테스트 통합
개발자 데스크톱에서만 테스트를 실행하는 테스트 스위트는 제안일 뿐이다.
테스트 스위트가 작동하려면, 모든 pull request가 같은 환경에서 동일한 체크를 실행하고, 체크가 실패하면 병합을 차단해야 한다. 이것은 명백하지만, 많은 팀이 여전히 중요한 결함을 남겨두고 있다. 테스트는 수동으로 실행된다. 커버리지 보고서는 선택사항이다. 패키징 및 릴리스 작업은 테스트 작업이 완료되기 전에 시작된다. 이것이 작은 UI regressions가 더 큰 릴리스 실패로 침투하는 방법이다.

Pull request는 항상 동일한 게이트를 트리거해야 합니다.
React 단위 테스트가 안전망으로 작동하려면 CI는 몇 가지 필수 요소를 필요로 합니다:
- Pull request마다 실행
- lockfile에서 의존성을 설치
- 테스트 명령어를 항상 동일하게 사용
- 테스트 실패 시 빠르게 실패
- 테스트 통과 후에만 아티팩트를 공개
이것은 앱 팀의 계속적인 배포 관행의 핵심입니다.. 배포 전에 자신감을 얻으세요, 배포 후에만.
많은 팀에게는 간단한 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
이것은 멋지지 않다. 그리고 그것이 목적이다. 가장 강력한 pipe line은 일반적으로 가장 놀라지 않는 pipe line이다.
이것이 Capacitor와 Electron에 더 중요한 이유는 무엇인가?
크로스 플랫폼 React 앱은 브라우저 전용 앱보다 릴리즈 리스크가 더 많다. 왜냐하면 같은 UI code가 종종 다른 컨테이너에 다른 런타임 가정과 함께 배포된다.
몇 가지 예시를 통해 pipe line이 도움이 되는지 보여준다.
- Capacitor 앱: code 앱은 로컬에서 통과하지만 플러그인 브리지를, 오프라인 상태, 또는 앱 라이프 사이클의 에지 케이스 변경이 패키징 후에 동작을 변경할 때 실패한다.
- Electron 앱: 렌더러 컴포넌트는 로드 프리로드 API, 윈도우 메시징, 또는 데스크톱 전용 상태에 의존할 수 있다. 이 상태는 평범한 브라우저 테스트에서 존재하지 않는다. 따라서 의도적으로 모킹해야 한다.
- 공유 릴리즈 트레인: 하나의 나쁜 배포가 여러 대상에 영향을 줄 수 있다. 만약 배포 프로세스가 출판을 강하게 게이트하지 않는다면.
이것이为什么 유닛 테스트가 패키징 작업 전에 실행되어야 하고, 패키징 작업이 배포 작업 전에 실행되어야 하는 이유이다. 각 단계는 리스크를 줄인다. 유닛 테스트는 로컬 리그레션을 빠르게 잡는다. 플랫폼 패키징은 환경 가정의 검증을 한다. 마지막으로 릴리즈에 대한 신뢰를 확보하기 위해 수동 승인 또는 스테이지드 롤아웃을 사용한다.
GitHub 액션 워크플로우
A 더 성숙한 pipeline은 일반적으로 책임을 나누어 관리합니다:
- 테스트 작업: 빠른 단위 및 훅 테스트
- 빌드 작업: 테스트가 통과한 후만 프로덕션 빌드를 수행합니다.
- 패키지 작업: Capacitor 동기화, Electron 패키징 또는 아티팩트 번들링
- 릴리스 작업: 만약에 승인된 branch 또는 tag에서만 릴리스합니다.
실시간 업데이트를 제공하는 Capacitor 또는 Electron 앱을 배포하는 팀에게는 릴리스 도구가 중요합니다. 그 workflow에서 하나의 옵션은 CapgoCapacitorJS 및 Electron 앱에 대한 서명된 웹 번들을 발행하고 롤백 지원 및 채널 기반 롤아웃 제어를 제공하는 것입니다. 실제로, React 테스트 작업은 스테이징 또는 프로덕션 배포로 승격되는 웹 번들의 첫 번째 강제 게이트 역할을 할 수 있습니다.
운영 규칙은 간단합니다. 릴리스 인프라가 약한 테스트를 보상하지 않도록 하세요. 신뢰할 수 있는 테스트 시스템이 이미 불량한 변경 사항을 필터링한 후 릴리스 인프라를 사용하세요.
신뢰할 수 있는 테스트 시스템은 팀의 행동을 바꿉니다. 엔지니어들은 더 적은 망설임으로 병합합니다. 리뷰어들은 에지 케이스에 집중하기보다는 수동으로 기초를 다시 실행하지 않습니다. 릴리스 매니저들은 모든 배포를 기대치로 다루지 않습니다. 이는 React 단위 테스트를 잘 수행했을 때의 결과입니다.
팀이 React를 Capacitor 또는 Electron을 통해 배포한다면, 릴리스 안전은 녹색 로컬 테스트만으로는 의존하지 않습니다. Capgo Capgo에서 PR을 제출하면