정의
"한 곳만 수정했는데 화면 전체가 다시 그려진다"는 React 성능 문제를, 눈이 아니라 자동 테스트로 감시하고 싶을 때 쓰는 기법이다. 특정 컴포넌트가 몇 번 다시 그려졌는지 숫자로 세어, 편집 후에도 관계없는 부분이 0번만 그려졌는지 검사한다.
용어를 풀면: 리렌더(re-render) 는 React가 컴포넌트 함수를 다시 실행해 화면을 갱신하는 것, 격리(isolation) 는 한 부분을 바꿔도 형제·조상 컴포넌트가 불필요하게 다시 그려지지 않는 상태를 말한다. jest.mock 은 테스트에서 특정 모듈을 가짜 버전으로 바꿔치기하는 Jest 기능, baseline 스냅샷은 "정상일 때의 기준값"을 미리 찍어둔 것이다.
정확히 말하면 React render count isolation testing은 이 리렌더 격리를 단위 테스트로 계약화(코드가 지켜야 할 약속으로 고정) 하는 방법이다. 대상 컴포넌트를 jest.mock으로 감싸 id별 렌더 횟수를 외부 Map에 기록하고, 값을 바꾸기 직전의 baseline 스냅샷과 이후를 비교한다. 포커스·마운트 같은 부수적 리렌더는 스냅샷 찍는 시점을 조절해 측정에서 분리한다.
맥락에서 이해하기
“깊은 블록만 편집했는데 화면 멀리 있는 형제도 다시 그려진다”는 불만은 눈으로는 잘 안 보인다. 프로파일러는 CI에 넣기 어렵고, “느려졌다”는 체감만으로는 회귀를 못 잡는다. 이 방법은 리팩터 전에 “먼 가지는 추가 리렌더 0회”를 테스트로 박아 두고, memo·read-model·store 분리를 넣은 뒤 같은 테스트가 GREEN인지로 개선을 숫자로 증명한다.
왜 필요한가
React.memo, selector, external store 최적화는 리팩터 한 번에 무너지기 쉽다. 프로파일러는 CI에 넣기 어렵고 E2E는 느리며 flake하기 쉽다. “깊은 노드만 수정했을 때 먼 형제 subtree는 0회 추가 렌더”처럼 구조적 불변을 테스트로 고정하면 O(depth)→O(1) 같은 회귀를 빠르게 잡는다.
동작 원리
- 갭분석: 기존 테스트가 flat vs nested, 격리 vs depth만 등 어떤 축을 커버하는지 표로 나눈다. 교집합(예: 재귀+값변경 격리) 만 새로 추가한다.
- Mock 프로브:
jest.mock('./RowChrome', () => ({ RowChrome: (props) => { counts.set(props.id, (counts.get(props.id) ?? 0) + 1); return ActualRowChrome(props); } }))— 원본은 실행, 카운트만 증가. - import 순서: mock은 호이스팅되므로 테스트 대상 import는 mock 아래 (
eslint-disable import/first패턴). - 시나리오: 트리 렌더 → (필요 시) focus로 editor 스왑 → baseline 스냅샷 → 결정적 값 변경 1회(Enter split 등) → far branch
expect(count).toBe(baseline). - 회귀: 아키텍처 개선 후 같은 테스트로 조상도 baseline 유지(O(1) 증명)를 요구할 수 있다.
실무 적용
- 트리·리스트·테이블 행 단위 memo·store 구독 설계 전후에 far branch 0 증가를 assert한다.
- typing 시뮬레이션보다 키보드 액션 한 번(split, toggle)이 jsdom에서 결정적이다.
- Provider를 mock해 컨테이너 render === 0을 측정할 때는 컨테이너와 1:1로 렌더되는 하위(Provider)를 프로브한다.
- 테스트 이름에 “격리”“O(1)” 의도를 명시해 나중에 스킵되지 않게 한다.
트레이드오프
- mock은 구현 세부에 결합된다 — 프로브 대상 컴포넌트 이름이 바뀌면 테스트도 수정.
- 전역
Map은 테스트 간beforeEach초기화 필요. - “0 추가 렌더”는 해당 시나리오에만 해당 — 다른 interaction 경로는 별도 테스트.
- React Strict Mode double-render는 setup에서 한 번 감안하거나 프로브 설계를 조정한다.
사용하면 안 되는 경우
- 스냅샷 전체 DOM 비교만으로 성능 계약을 대체하려 할 때 — 의도가 불명확하다.
- 프로브가 너무 많아 모든 래퍼를 mock하면 테스트가 구현 미러가 될 때.
흔한 실수
- baseline을 focus 전에 찍어 static→editor 스왑을 값 변경으로 오인.
- mock이 원본을 호출하지 않아 동작은 깨졌는데 카운트만 통과.
- flat 3형제 테스트만 있고 깊이+값변경 축을 비워 둠.
userEvent.type장문 입력을 flake 없이 격리 테스트에 사용.
관련 개념
- react-context-render-granularity — context·memo가 깨지는 맥락
- normalized-render-entry-projection — 격리를 만드는 read-model 패턴
- jest-esm-vendor-shim — Jest 환경에서 mock·import 이슈