홈으로 돌아가기

React Testing Library: 스냅샷 버리기

이 글은 프로젝트에서 Enzyme에서 React Testing Library로의 전환을 설명합니다. 스냅샷 및 구현 테스트 문제 분석. 테스트 작성 및 검토 체크리스트, 밋업 및 마이그레이션 추천.

Enzyme 대신 RTL: React 동작 테스트
Advertisement 728x90

스냅샷에서 사용자 행동으로: React Testing Library로 전환하기

일반적인 프로젝트에서는 테스트가 마크업에 집중하는 경우가 많습니다. 스냅샷 테스트는 DOM 상태를 캡처하지만, 리팩터링 시 즉시 깨집니다. CSS 클래스 변경, 속성 순서 조정, 렌더링 최적화 등 작은 변화도 테스트 실패를 유발합니다. 실제로 발생하는 로직 오류는 여전히 감지되지 않습니다.

Enzyme로 작성한 문제 있는 테스트 예시

describe("UserProfileCards", () => {
    it("should render", () => {
        const wrapper = shallow(
            <Provider store={store}>
                <UserProfileCards items={mockedUserProfiles} />
            </Provider>
        )
        expect(wrapper).toMatchSnapshot()
    })
})

이 테스트는 컴포넌트의 동작에 대한 실질적인 신뢰를 주지 못합니다. 구조만 확인할 뿐, 사용자가 실제로 경험하는 바는 전혀 반영되지 않습니다.

구현 결합: 잘못된 성공 신호

내부 구현 세부 사항에 의존하는 테스트는 두 가지 문제를 야기합니다. 리팩터링 시 실패하고, 실제 버그는 놓칩니다.

Google AdInline article slot

CollapseIcon 컴포넌트가 opened 상태를 가진다고 가정해 봅시다:

import CollapseIcon from "./CollapseIcon"
import { shallow } from "enzyme"

describe("CollapseIcon", () => {
    it("should render", () => {
        const wrapper = shallow(<CollapseIcon opened={false} />)
        expect(wrapper).toMatchSnapshot()
    })

    it("should react to closed", () => {
        const wrapper = shallow(<CollapseIcon opened={false} />)
        expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_closed")).toEqual(true)
    })

    it("should react to opened", () => {
        const wrapper = shallow(<CollapseIcon opened={true} />)
        expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_opened")).toEqual(true)
    })
})

문제점:

  • 스냅샷은 애니메이션 로직 오류를 감지하지 못함.
  • 스타일 리팩터링 시 클래스 이름 검사가 깨짐.
  • 애니메이션이 멈춰 있어도 테스트는 통과됨.

다른 사례: 지역화 상수에 강하게 결합된 테스트:

Google AdInline article slot
it("should render no-answers button", () => {
    const button = getButton(mockCommentsList[0])
    const noAnswersText = `${mockedTranslation.t(`${tNamespace}no-answers`)}
    expect(button.text()).toBe(noAnswersText)
})

tNamespace 내 키를 변경하면 테스트가 깨지지만, 버튼 텍스트는 정확할 수 있습니다. 해결책: 상수를 컴포넌트와 테스트 모두가 가져오는 공유 모듈로 이동하세요.

모호한 테스트 설명은 상황을 더 악화시킵니다:

  • "should show tooltip" — 어떤 조건인지 명시 없음.
  • "should render without tooltipLabel" — 기대 결과 없음.

React Testing Library: 사용자 행동 테스트하기

RTL은 패러다임을 완전히 바꿉니다. 테스트는 실제 사용자의 행동을 시뮬레이션하고 내부 구현을 무시합니다. 상태, props, 메서드에 접근할 수 없으며, 오직 보이는 UI만 다룹니다.

Google AdInline article slot

RTL의 장점:

  • 사용자 중심: 클릭, 입력, 화면 독백 같은 실제 사용자 행동을 테스트.
  • 리팩터링 안전: 클래스 이름, ID, 내부 상태와 무관.
  • API: render(), screen.getByRole(), fireEvent.click(), waitFor().
  • 비동기 지원: 데이터 로딩에 findByRole() 활용 가능.
  • 접근성 강화: getByLabelText()로 올바른 라벨링 보장.

RTL은 내구성을 위해 설계되었습니다. 테스트는 DOM 구조가 아니라 실제 사용 방식을 반영합니다.

고품질 테스트를 위한 체크리스트

테스트 작성 시

  • 목적 명확화: 무엇을 테스트하는지 분명히 정의 (예: "isTooltipShow=true일 때 툴팁 표시")
  • 사용자 관점: 실제 사용자가 하는 행동처럼 (예: getByRole('button'), fireEvent)
  • 격리성: API는 모킹; 테스트 간 의존성 제거
  • 설명: 코드 없이도 이해 가능한 전체 문장으로 작성
  • 커버리지: 정상 경로 + 극단적 케이스 + 에러 상태 포함

테스트 검토 시

  • 목표가 명확한가?
  • 구현 세부사항(클래스, 상태)은 피했는가?
  • 격리되어 있는가?
  • 설명은 정보 제공이 되는가?
  • 주석은 건설적인가?

팀 전환을 위한 조직화 전략

마이그레이션 워크숍 개최: 문제 있는 테스트 모아 분석, RTL로 전환. 추천 사항:

  • 45분 단위로 세션 나누고, 실습 중심으로 진행.
  • 페어 프로그래밍으로 함께 리팩터링.
  • 이론보다 실습을 우선시.

레거시 마이그레이션: 새 테스트는 RTL로 작성, 점차 기존 테스트 재작성. 에어비앤비 사례: LLM 기반 파이프라인을 활용해 6주 만에 3,500개 파일 리라이팅 (75% 자동화).

핵심 요약

  • 사고방식 전환: 마크업에서 사용자 행동으로.
  • RTL vs Enzyme: RTL은 내부 접근 차단; Enzyme은 이를 유도.
  • 체크리스트: 작성 및 검토 필수.
  • 워크숍: 대화가 일방적 설명보다 효과적.
  • 마이그레이션: 자동화는 속도를 높이지만, 철학적 이해가 필수.

— Editorial Team

Advertisement 728x90

다음 읽기