컴포넌트 테스팅 시작하기

컴포넌트 테스팅 시작하기 (Cypress Component Testing)

페이지 전체를 불러오는 대신, 화면을 이루는 낱개 조각 컴포넌트 하나만 골라서 테스트하고 싶을 때가 있어요. Cypress 컴포넌트 테스팅은 그 컴포넌트를 실제 브라우저에 마운트(mount)해서, 개발할 때처럼 DevTools로 확인하고 조작하고 디버깅할 수 있게 해줘요. 이 글에서는 컴포넌트 테스팅을 왜 쓰는지, 어떻게 설정하고 첫 테스트를 작성하는지 하나씩 살펴볼게요.

출처: Cypress 공식 문서 — Get started with component testing 원본 URL: https://docs.cypress.io/app/component-testing/overview

왜 Cypress로 컴포넌트를 테스트할까요

컴포넌트 테스팅의 장점은 테스트 실행 중에도 컴포넌트가 시각적으로 렌더링된다는 점이에요. 손으로 직접 조작하고, DevTools로 요소를 검사하고, Time Travel로 테스트의 각 시점을 되짚어 볼 수 있죠. console.log를 찍어가며 추적할 필요가 없어요.

  • 테스트 보일러플레이트가 줄어요. 자동 대기(auto-waiting) 덕분에 컴포넌트가 렌더링되고 갱신된 뒤에만 단언이 실행돼요. waitForact(), 수동 타임아웃이 필요 없죠. 무엇이 참이어야 하는지 설명하면 Cypress가 그 순간까지 기다렸다가 확인해요.
  • 스파이와 스텁이 내장돼 있어요. 이벤트 핸들러가 올바르게 호출되는지 확인하고, 의존성을 격리하는 데 강력한 도구예요.
  • 프로젝트 하나에 품질 신호도 하나예요. 컴포넌트 테스트가 엔드투엔드 테스트와 같은 Cypress 프로젝트 안에 함께 살아요.
  • 버그를 일찍 잡아요. 컴포넌트 하나의 버그를 격리된 상태에서 잡는 비용이, 엔드투엔드 테스트나 배포 후에 잡는 것보다 훨씬 저렴합니다.

컴포넌트 테스팅 설정하기

Cypress를 처음 실행하면 E2E 테스팅과 컴포넌트 테스팅 중 하나를 고르라는 안내가 나와요. Component Testing을 클릭하면 설정 마법사가 시작됩니다.

이때 주의할 점 하나. 컴포넌트 테스트가 모두 통과한다고 해서 앱 전체가 제대로 동작한다는 뜻은 아니에요. 컴포넌트 테스트는 각 계층이 함께 잘 맞물려 있는지는 보장하지 못합니다. 그래서 잘 테스트된 앱은 엔드투엔드와 컴포넌트 테스트를 적절히 조합해서 쓰게 돼요.

첫 번째 컴포넌트 테스트

설정이 끝나면 아직 스펙이 하나도 없어서 Cypress가 찾는 스펙이 없을 거예요. 프레임워크마다 차이는 있지만, 테스트를 쓰는 방법은 대체로 비슷합니다. StepperComponent를 예로 들어 기본 테스트를 볼게요.

import React from 'react'
import Stepper from './Stepper'

describe('<Stepper />', () => {
  it('mounts', () => {
    cy.mount(<Stepper />)
  })
})

먼저 Stepper 컴포넌트를 임포트하고, describeit으로 테스트를 묶어요. 이 함수들은 Cypress가 제공하는 전역 함수라서 따로 임포트할 필요가 없어요. 최상위 describe 블록이 파일의 모든 테스트를 담는 컨테이너 역할을 하고, 각각의 it이 개별 테스트를 나타냅니다. 여기서 핵심은 cy.mount()인데, 이 메서드가 컴포넌트를 테스트 앱에 마운트해서 테스트를 시작할 수 있게 해줘요.

요소를 선택하고 검증하기

컴포넌트를 마운트만 했다면 동작은 검증하지 않은 상태예요. 카운터가 담긴 span 요소를 선택해서 그 텍스트 값을 확인해 볼게요. DOM에서 요소를 고르는 방법은 여러 가지인데, CSS 스타일 셀렉터를 넘기는 cy.get()을 쓰면 됩니다.

it('stepper should default to 0', () => {
  cy.mount(<Stepper />)
  cy.get('span').should('have.text', '0')
})

Test Runner로 돌아가면 테스트가 통과하는 걸 볼 수 있어요.

상호작용 테스트하기

이제 사용자가 버튼을 클릭하면 카운터 값이 변하는지 확인해 볼게요. .click() 커맨드로 실제 사용자처럼 DOM 요소를 클릭합니다.

it('when the increment button is pressed, the counter is incremented', () => {
  cy.mount(<Stepper />)
  cy.get('[data-cy=increment]').click()
  cy.get('[data-cy=counter]').should('have.text', '1')
})

it('when the decrement button is pressed, the counter is decremented', () => {
  cy.mount(<Stepper />)
  cy.get('[data-cy=decrement]').click()
  cy.get('[data-cy=counter]').should('have.text', '-1')
})

이벤트까지 검증하기 — 스파이 사용

Stepper의 개발자라면 사용자가 증가/감소 버튼을 눌렀을 때 소비하는 쪽 컴포넌트로 적절한 이벤트가 올바른 값과 함께 전달되는지도 확인하고 싶을 거예요. 이때 Cypress의 **스파이(Spy)**를 사용합니다. 스파이는 호출된 횟수와 호출 시 전달된 파라미터를 기록하는 특수 함수예요.

it('clicking + fires a change event with the incremented value', () => {
  const onChangeSpy = cy.spy().as('onChangeSpy')

  cy.mount(<Stepper onChange={onChangeSpy} />)
  cy.get('[data-cy=increment]').click()
  cy.get('@onChangeSpy').should('have.been.calledWith', 1)
})

cy.spy()로 새 스파이를 만들고 .as('onChangeSpy')로 별칭을 붙여요. 별칭은 나중에 참조할 이름을 지정하는 거예요. cy.mount()에서 컴포넌트를 초기화하면서 스파이를 넘기고, 증가 버튼을 클릭한 뒤 cy.get('@onChangeSpy')로 스파이를 찾아 have.been.calledWith(1)을 확인합니다.

더 알아보기