반응형

 

리액트 컴포넌트 테스트를 위한 jest와 cypress의 사용 경험 비교

 

리액트 컴포넌트 테스트를 위해 jest + react testing librarycypress를 각각 써본 후 느낀 점들이 있습니다. 이번 포스팅에서는 이 둘을 개발 경험, 코드, 테스트 유효성 관점에서 어떤 차이가 있는지 비교해보려고 합니다.

 

 

 

개발 경험 비교

 

1. 환경설정

 

jest는 워낙 올인원 프레임워크라 preconfiguration 코드가 필요 없고, dependencies를 추가 설치할 필요가 없습니다. 그리고 만약 CRA (Create React App)로 프로젝트를 시작하면 jest와 react testing library가 기본적으로 설치가 되어있기 때문에 CRA로 init을 하자마자 바로 테스트 코드를 작성할 수 있습니다. CRA에서 테스트코드 개발 모듈로 이 둘을 기본적으로 제공해주는 이유는 이 둘을 페이스북에서 공식적으로 사용하라고 추천하고 있기 때문인 것 같습니다.

 

cypress는 설치 직후 E2E 테스트 코드는 바로 작성할 수 있지만, 컴포넌트 테스트를 하려면 추가적인 의존 모듈, 플러그인, config 설정이 필요합니다.

 

* 추가 모듈

yarn add -D @cypress/react @cypress/webpack-dev-server

 

cypress 컴포넌트 테스트를 하기 위해 필요한 추가 모듈, 플러그인, config 설정, 그리고 컴포넌트 테스트 작성 방법에 대해 다음 블로그 글은 쉽고 자세하게 설명해주고 있습니다.

https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header

 

Getting Started with Cypress Component Testing (React)

As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress! It builds on our learnings from the original component testing implementation, which was hidden behind the `experimentalComponentTesting` flag.

www.cypress.io

 

cypress는 test runner로서 jest가 아닌 mocha를 사용하고 있습니다. mocha는 프레임워크가 아닌 테스트 러너기 때문에 다른 의존 모듈들이 필요합니다. cypress가 mocha를 선택한 이유는 mocha는 라이브러리 선택 등에 대해 자유롭고 유연함을 가지고 있기 때문이라고 생각하고 있습니다. cypress는 chai, sinon 등 의존 모듈들을 내부적으로 사용하고 있기 때문에 우리가 따로 이것들을 설치할 필요는 없습니다.

 

 

 

2. 개발 편의성

 

jest는 터미널에서 watch & hot reload 모드를 사용할 수 있고, cypress는 브라우저에서 watch & hot reload 모드를 사용할 수 있습니다.

watch all은 jest, cypress 둘 다 가능하지만, cypress의 컴포넌트 테스트는 모든 테스트 파일을 hot reload 하는 hot reload all files가 안됩니다. cypress는 E2E test의 경우는 hot reload all files가 가능하지만, component test의 경우는 하나의 파일에 대해서만 hot reload를 할 수 있습니다. 프로젝트 규모가 크고 테스트 파일과 코드가 많으면 모든 파일을 hot reload 하는 것은 시간문제 때문에 선택되지 않기도 하지만, 이것은 기본적으로 휴먼 에러를 차단할 수 있는 좋은 옵션인 것은 사실입니다.

 

cypress는 브라우저 위에서 탐색/디버깅이 가능하기 때문에, 리액트 앱 로컬 서버를 띄우지 않은 상태에서도 크롬 개발자 도구를 보면서 개발이 가능합니다.

 

cypress의 유닛 테스트 watch & hot reload 모드는 jest의 watch & hot reload 모드보다 메모리를 더 많이 사용합니다. 램 사양이 낮은 컴퓨터면 고려사항이 될 수 있습니다.

 

 

 

 

 

 

코드 비교

 

컴포넌트 테스트는 다음과 같은 과정으로 진행했으며, 스냅샷테스트는 하지 않았습니다.

 

  1.  props값에 따른 렌더링 테스트
  2. 액션 이후 렌더링 테스트

 

따라서 이번에 소개할 코드 목록은 다음과 같습니다.

 

  1. 컴포넌트 셋업 with props
  2. 렌더링 결과 확인
  3. UI로 행동 발생

 

간단한 버튼 컴포넌트의 테스트 코드를 통해 둘을 비교해보겠습니다.

 

 

 

jest + react testing library 버튼 컴포넌트 테스트 코드

 

* 예시 코드

import { fireEvent, render } from "@testing-library/react";

import Button from "./Button";

describe("Button", () => {
  it("Given Button, When Click, Then call onClick", () => {
    const onClick = jest.fn();
    const { getByText } = render(<Button onClick={onClick}>Test Button</Button>);
    const button = getByText(/Test button/i);

    expect(button).toBeTruthy();

    fireEvent.click(button);

    expect(onClick).toHaveBeenCalledTimes(1);
  });
});

 

1. 컴포넌트 셋업 with props

 

컴포넌트를 마운트 시키는 것은 react testing library에서 제공하는 render함수로 쉽게 할 수 있습니다. 인자 값으로 ReactElement를 전달하면 됩니다.

 

 

2. 렌더링 결과 확인

 

render함수가 반환하는 여러 메서드들이 있습니다. 이 메서드들은 DOM Element에 접근할 수 있는 여러 방법들을 제공합니다. 위 예시 코드에서는 Text로 Element에 접근하는 메서드인 getByText를 사용했습니다. 이것으로 버튼 엘리먼트를 button변수에 초기화시켰습니다.

 

렌더링 결과를 검증하기 위해서 expect()에 버튼 엘리먼트를 넣고, toBeTruthy 함수로 존재하는지를 확인했습니다. 테스트는 버튼이 존재한다면 pass, 존재하지 않는다면 fail 할 것입니다.

 

 

3. UI로 행동 발생

 

다음으로 react testing library에서 제공하는 fireEvent함수로 버튼 엘리먼트에 click 이벤트를 발생시켰습니다. 이벤트를 발생시키는 함수로 fireEvent말고 userEvent함수를 사용하는 방법도 있습니다.

 

userEvent는 마치 사람이 직접 브라우저 상에서 행동하는 것처럼 유저 이벤트를 발생시킬 수 있습니다. 따라서 fireEvent는 코드로 명시한 click 이벤트만 발생시키지만, userEvent는 유저가 click을 할 때 실제로 발생하는 다른 이벤트들도 모두 발생합니다. 따라서 userEvent를 사용하는 것이 더 실제 환경에 가깝게 테스트를 하는 것입니다.

 

* userEvent 사용

import userEvent from "@testing-library/user-event";
// ...(중략)
userEvent.click(button);

 

 

 

cypress 버튼 컴포넌트 테스트 코드

 

* 예시 코드

import { mount } from "@cypress/react";

import Button from "./Button";

describe("Button", () => {
  it("Given Button, When Click, Then call onClic", () => {
    const onClick = cy.stub().as("clickHandler");
    mount(<Button onClick={onClick}>Test button</Button>);

    cy.get("button").contains("Test button").click();

    cy.get("@clickHandler").should("have.been.calledOnce");
  });
});

 

1. 컴포넌트 셋업 with props

 

컴포넌트를 마운트 시키는 것은 cypress에서 제공하는 mount함수로 쉽게 할 수 있습니다. 인자 값으로 ReactNode를 전달하면 됩니다. ReactElement를 받는 react testing library의 render함수와는 차이가 조금 있습니다.

 

 

2. 렌더링 결과 확인

 

cypress는 DOM Element에 접근하기 위해서 단 하나의 메서드만을 사용하면 됩니다. cy.get()은 jQuery의 DOM 쿼리 방식을 그대로 사용합니다. 그리고 체이닝을 할 수 있기에 contains메서드로 특정 텍스트를 가지고 있는 button을 필터링하여 얻을 수 있습니다. contains다음에 should메소드로 체이닝하면 단언을 작성할 수 있습니다. 다음과 같이 작성하면 Test button 텍스트를 가진 button 태그가 존재하는지를 확인하는 테스트가 됩니다.

 

cy.get("button").contains("Test button").should("exist");

 

cypress는 단언을 should와 and로 작성할 수 있습니다. and는 should 뒤에 추가적인 단언을 체이닝으로 작성하고 싶을 때 사용합니다.

 

 cy.get("input").should("be.focused").and("have.value", "Hello");

 

 

3. UI로 행동 발생

 

하지만 contains 다음 체이닝으로 단언을 쓰지 않고 click 메소드로 click 이벤트를 발생시켰습니다. 이렇게 해도 contains에서 어떤 엘리먼트도 얻지 못하면 테스트는 fail 하게 됩니다.

 

 

 

테스트 유효성

 

둘 다 DOM에서 테스트하지만 cypress는 브라우저 위에서 실행하며, 실제 유저 입장에서 다양한 브라우저와 다양한 상황에서 테스트할 수 있어서 더 유효합니다.

 

 

 

결론

 

React component test를 하기 위해 환경설정, 개발 편의성, 예시 코드, 테스트 유효성 관점에서 jest + react testing library와 cypress를 비교해봤습니다. 어떤 관점에 더 우선순위를 두는지는 사람마다 그리고 처한 상황마다 다를 것 같습니다. React component test를 하기 위해 어떤 기술을 사용할지 선택하는 것은 개인적으로 매우 어려웠습니다. 다양한 기술들이 존재하며, 그들의 특징과 장단점이 뚜렷하기 때문에 완벽한 하나의 솔루션이 없기 때문입니다. 그래도 이런 기술 비교와 고민의 시간을 가지는 것은 다음에 진행할 리액트 프로젝트들을 위해 더 넓은 시야를 갖게 만드는 것 같습니다.

 

 

 

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기