Please enable JavaScript to view the comments powered by Disqus.

[번역] 초보자를 위한 React 어플리케이션 테스트 심층 가이드 (1)

with @testing-library/react

이 글은 ”An in-depth beginner’s guide to testing React applications“를 번역한 글입니다


대부분의 개발자는 다음의 사실을 알고 있다: 자동화된 테스트는 중요하다. 그렇게 주장하는 데는 많은 이유가 있다.

  • 단지 코드 몇 줄을 바꾸는 것으로 자기도 모르게 앱을 망가뜨릴 수도 있다.
  • 코드를 수정한 후에 테스트를 수동으로 돌리는 일은 성가시다.
  • 테스트는 edge case1를 문서화 하는 데 도움을 준다.
  • 이직을 원하는 개발자 입장에서 다른 지원자보다 더 높은 경쟁력을 가질 수 있다.

안타까운 얘기지만, 테스트 케이스를 작성하는 일은 처음 시도하는 사람에게 쉬운 일이 아니다. 그것은 마치 완전히 다른 환경에서 개발을 진행하는 것과 유사하다. 당신은 대체 무엇을 테스트해야 할지 모를 수도 있다. 간단한 테스트 케이스를 작성에도 에러가 계속 발생해서 많은 시간이 걸릴 수도 있다. 당신은 브라우저를 기반으로 개발하는 일에 익숙하다. 브라우저는 물론 훌륭한 도구다. 하지만 테스트는 브라우저에서 작성하는 것과 같은 방식으로 동작하지 않는다.

하지만 테스트가 그렇게 어려울 것은 없다. 올바른 접근법을 가진다면 초보자라도 확신을 가지고 테스트를 작성할 수 있다.

이 블로그 포스트의 목표는 당신의 첫 번째 테스트를 위한 가이드가 되는 것이다. 우리는 조그만 예제 앱과 함께 테스트를 작성해 나갈 것이다. 그리고 무엇을 테스트 해야 할지, 무엇을 테스트 하면 안되는지에 대해 이야기 할 것이다. 우리는 시행착오 없이 확신을 갖고 테스트를 작성할 수 있는 다양한 테크닉을 살펴보려 한다.

이 블로그 포스트는 꽤 길고 심층적인 내용을 다루고 있다. 아래의 차례를 통해 어떤 내용이 나올지 미리 확인할 수 있을 것이다.

  1. React 앱 테스팅 개요
  2. 테스트에 사용할 어플리케이션
  3. 무엇을 테스트해야 하는가?
  4. 테스트 작성
  5. 어둠 속에서 찔러보지 말라
  6. 렌더링된 DOM 트리에 접근하는 방법
  7. DOM 요소와 상호작용 하기
  8. 올바른 페이지가 렌더링 되었는지 확인하기
  9. 폼 테스팅
  10. 셋업 함수에서 중복 방지하기
  11. 폼을 변경하고 제출하기
  12. ARIA role 없이 요소에 접근하기
  13. 데이터 기다리기
  14. API 요청 위조하기(mocking)
  15. Mock 함수 테스팅

이 포스트는 많은 정보를 담고 있다. 구체적인 내용을 기억하는데 도움을 주기 위해 나는 모든 팁과 리소스의 목록을 담은 한장의 요약본(cheatsheet)를 만들었다. 그것은 글 마지막 부분에서 얻을 수 있다. 2

테스트할 어플리케이션을 살펴보기 전에 일반적인 React 앱을 테스트하는 방법에 대해 전반적으로 살펴보자.

React 앱 테스팅 개요

큰 규모의 앱을 작업할 때 중요한 파트의 코드를 건드리는 일은 무섭게 느껴질 수 있다. 사소한 수정도 매우 중요한 기능을 망가뜨릴 수 있기 때문이다. 개발자는 이런 위험을 최소화하기 위해 테스트를 작성한다.

테스트 작성의 목적은 당신에게 그 앱이 제대로 동작한다는 확신을 주는 데 있다. 만약 모든 중요한 유즈케이스(use-case)가 테스트로 커버되어 있다면 당신이 무언가를 망가뜨렸을 때 빠르게 피드백을 얻을 수 있을 것이다.

나는 이런 테스트가 개발자와 비지니스에 설명할 수도 없을 정도로 큰 이득을 준다고 힘주어 말할 수 있다.

현재 React 앱의 자동화된 테스트에 꼭 맞는 라이브러리는 Jest@testing-library/react(aka Testing Library)의 조합이다.

물론 다른 라이브러리도 있다. Jest는 Mocha, Jasmine, 또는 AVA로 대신할 수 있고, Testing Library는 많은 개발자들이 사용하고 있는 Enzyme으로 대체할 수 있다.

Testing Library는 테스트를 사용자 관점에서 접근한다. 그러므로 그것은 우리를 자연스럽게 복수의 컴포넌트가 함께 테스트되는 Integration Test3로 이끈다.

예를 들어 어떤 버튼을 생각해보자. Testing Library를 사용하면 버튼이 클릭되었을 때 onClick prop이 호출되는지는 잘 테스트하지 않는다. 그보다 특정 버튼이 특정 효과를 발동시키는지 확인한다. 예를 들어 삭제 버튼을 클릭하면 확인 모달이 열리는지 테스트한다.

테스트에 사용할 어플리케이션

[2020-10-14] 1,6-react-testing-intro-2

이 앱은 ooloo.io에 있는 내 강의에서 개발하는 앱의 간단한 버전이다. 만약 준비가 되지 않은 것 같다면, 전문적인 팀이 어떻게 실무에서 사용하는 앱을 개발 하는지 배울 수 있다.

이 앱은 사용자에게 서브레딧(subreddit)에서 가장 인기있는 포스트를 찾을 수 있도록 한다. 이것은 매우 간단한 앱으로 제목과 몇 개의 링크, 폼을 가지고 있을 뿐이다. 하지만 당신의 첫 번째 테스트를 위해서 좋은 예제가 될 것이다.

헤더에 있는 다른 페이지를 가리키는 링크는 단지 헤드라인만 포함한 플레이스홀더 이상의 역할을 한다. 중요한 기능은 바로 다른 페이지로의 네비게이션이다.

1개의 텍스트 입력을 가진 폼에서는 사용자가 검색할 서브레딧의 제목을 입력할 수 있다.

제출(submit) 버튼을 클릭하면 Reddit API로 요청을 보낸다. 응답을 기다리는 동안 앱은 로딩 상태를 표시한다. 데이터는 응답을 받는 대로 표시되며, 단순함을 위해 상위 포스트의 개수만 표시한다.

전체 코드는 이 저장소에서 확인할 수 있다. 원한다면 저장소를 클론한 후에 이 글을 계속 읽어도 좋다.

무엇을 테스트해야 하는가?

처음으로 떠오르는 질문은 “무엇을 테스트해야 하는가”일 것이다. 예제로 앱에 있는 폼 컴포넌트를 살펴보자. 코드는 아래와 같다.

function Form({ onSearch }) {
  const [subreddit, setSubreddit] = useState('javascript');

  const onSubmit = (event) => {
    event.preventDefault();
    onSearch(subreddit);
  };

  return (
    <FormContainer onSubmit={onSubmit}>
      <Label>
        r /
        <Input
          type="text"
          name="subreddit"
          value={subreddit}
          onChange={(event) => setSubreddit(event.target.value)}
        />
      </Label>

      <Button type="submit">
        Search
      </Button>
    </FormContainer>
  );
}

폼의 입력값은 상태 변수(subreddit)와 계속 연동된다. 제출 버튼을 클릭하면 상위 컴포넌트에서 전달된 onSearch prop을 호출한다.

어떻게 데이터를 가져오는지도 궁금할 것이다. 그것은 폼 컴포넌트의 부모인 홈 페이지 컴포넌트에서 이뤄진다.

function Home() {
  const [posts, setPosts] = useState([]);
  const [status, setStatus] = useState('idle')

  const onSearch = async (subreddit) => {
    setStatus('loading');
    const url = `https://www.reddit.com/r/${subreddit}/top.json`;
    const response = await fetch(url);
    const { data } = await response.json();
    setPosts(data.children);
    setStatus('resolved');
  };

  return (
    <Container>
      <Section>
        <Headline>
          Find the best time for a subreddit
        </Headline>

        <Form onSearch={onSearch} />
      </Section>

      {
        status === 'loading' && (
          <Status>
            Is loading
          </Status>
        )
      }
      {
        status === 'resolved' && (
          <TopPosts>
            Number of top posts: {posts.length}
          </TopPosts>
        )
      }
    </Container>
  );
} 

홈 페이지 컴포넌트는 API 응답 데이터를 상태 변수에 저장하며 로딩 상황을 계속 추적한다. 폼에 의해 검색이 실행되면 Reddit API로 요청이 전달된다. 데이터가 도착하면 상태 변수들(posts, status)이 모두 업데이트되며 결과가 폼 아래에 표시된다.

이제 당신은 코드에서 중요한 부분을 살펴보았고 더 진행하기 전에 스스로 다음의 질문의 답해 보기 바란다: 당신은 저 두 컴포넌트를 어떻게 테스트하겠는가?

우리는 아마도 위의 컴포넌트를 살펴보고선 유닛 테스트를 먼저 작성하려 들 것이다. 우리는 아마 상태가 제대로 저장되는지 확인하고 현재의 subreddit 값과 함께 onSearch prop이 호출되는지 확인하길 원할 것이다. 이것이 많은 개발자들이 Enzyme으로 테스트하는 방식이다.

하지만 Testing Library를 사용한다면 상태 변수에 접근하지 않는다. prop으로 전달된 함수를 테스트하긴 할 것이지만, 상태 변수가 정확한 값을 가지고 있는지는 테스트하지 않는다.

이것은 약점이 아니다. 이것은 강점이다. 상태 관리는 컴포넌트의 구현 세부사항(implementation detail)이다. 상태 변수가 지금은 컴포넌트에 있지만 부모로 옮겨질 수도 있고 그렇게 해도 앱은 똑같이 동작해야 한다4.

사실 React 자체도 구현 세부사항이다. 우리는 앱 전체를 유저가 모르는 사이에 Vue.js로 옮길 수도 있다.

앱의 코드와 그것의 구현 방식에 집중하는 대신, 우리는 단순히 유저의 관점을 취한다. 그리고 이 관점은 우리가 앱의 중요한 부분을 테스트하는데 집중하도록 강제로 이끈다.

우리가 이 테스팅 철학을 받아들인다면 테스팅은 더 이상 알기 어렵고 무서운 것이 아니게 된다.

노트: 사용자는 어플리케이션의 말단 사용자일 수도 있고 당신이 개발한 컴포넌트를 사용하는 개발자가 될 수도 있다. 당신이 소속된 팀의 다른 개발자들도 사용하는 이미지 갤러리를 개발했다고 가정해보자. 예를 들어 당신은 props가 변경되었을 때 앱이 제대로 동작하는지 테스트해야 한다.

좋다, 사용자 관점이다. 그러니 어플리케이션에서 저 컴포넌트에 대해서는 잠시 잊고 사용자가 직면하는 부분에 대해 집중해 보자. 사용자 입장에서 앱이 제대로 기능하기 위해서는 무엇이 중요할까?

[2020-10-14] 2-react-testing-intro-form

일단 테스트할 앱을 클릭해서 사용해 보자. 앱 테스트에서 중요한 것은 그렇게 직접 사용해 본 내용들이다. 그에 대해서는 이미 위에서 설명했었다.

  1. 사용자는 폼 입력에 값을 입력하고 제출 버튼을 누른다.
  2. 데이터를 기다리는 동안 로딩 메시지가 표시된다.
  3. API 응답이 도착하면 데이터가 렌더링된다.

사용자는 홈 페이지나 폼 컴포넌트가 입력 값을 저장하는지 아닌지는 신경쓰지 않는다. 사용자 입장에서 응답으로 받은 포스트 데이터가 상태 변수에 저장이 되는지, 데이터 구조가 어떤지는 상관할 바가 아니다. 유저에게 중요한 것은 오직 위의 세 단계다.

물론, 우리는 헤더에 있는 링크도 테스트해야 한다. 어찌됐든 깨진 링크(예. 회원 가입 페이지로 가는 링크)는 비지니스에 문제를 줄 수 있기 때문이다.

노트: 보통 우리는 엣지 케이스를 테스트해야 하고 폼에서 발생하는 에러도 처리해야 한다. 하지만 그것까지 이 블로그 포스트에서 다루기에는 너무 양이 많다.

테스트 작성

지난 섹션의 내용을 빨리 훑어보고 알게 된 것들을 기술적인 언어로 풀어 보자:

우리는 2개의 테스트 묶음(suite)를 작성할 것이다. 하나는 헤더 링크를 위한 것이며 나머지 하나는 폼을 위한 것이다. 헤더를 위해서는 링크가 정확한 대상을 가리키는지 테스트해야 한다. 그리고 폼을 위해서는 입력 값 변경, 값 제출, 로딩 상태, 그리고 결과 렌더링을 테스트해야 한다.

헤더를 위한 테스트부터 시작해 보자. 먼저, src/app.js 를 열어서 작성되어 있는 테스트를 제거한다. 그리고, 헤더 테스트 묶음을 Jest의 describe 함수를 사용해서 정의하자.

노트: 테스트를 꼭 describe로 감쌀 필요는 없지만, 이 방법은 관련 있는 테스트를 한곳에 모아 준다. 그리고 터미널 출력을 보다 읽기 쉽게 만들어 주며 반복적인 테스트 메시지 출력을 막아 준다. 만약 테스트 파일이 길어지면 에디터에서 필요없는 테스트 코드 블록을 접어서 가독성을 높일 수도 있다.

describe('Header', () => {

});

테스트 케이스는 test(...) 로 정의될 수 있다. 그리고 it(...) 으로 대체할 수도 있다. Jest는 둘 다 제공한다.

describe('Header', () => {
  test('"How it works" link points to the correct page', () => {

  });
});

우리는 헤더 컴포넌트를 분리된 상태가 아닌 어플리케이션 컨텍스트 안에서 테스트하고 싶다. 그것이 우리가 이 테스트에서 App 컴포넌트를 사용해야 하는 이유다.

App 컴포넌트는 아래와 같이 작성되어 있다.

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import GlobalStyle from './GlobalStyle';
import Header from './components/Header';
import Home from './pages/Home';

function App() {
  return (
    <>
      <GlobalStyle />
      <Header />

      <main>
        <Switch>
          <Route path="/how-it-works">
            <h1>How it works</h1>
          </Route>
          <Route path="/about">
            <h1>About</h1>
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </main>
    </>
  );
}

이 App 컴포넌트는 실제로 운영되고 있는 많은 어플리케이션처럼 React Router를 사용한다. 그리고 헤더 컴포넌트와 홈 페이지를 포함한 몇 개의 라우트를 렌더링한다.

참고로 여기에는 Router 컴포넌트가 없다. 테스트를 위해 Router는 App 컴포넌트를 렌더링하는 index.js 파일에 있다. 테스트 중에는 App 컴포넌트를 MemoryRouter로 감쌀 것이다.

그래서 첫 번째 단계로, 우리는 앱 컴포넌트를 렌더링한다. Testing Library는 주어진 컴포넌트로 DOM을 생성하는 render 함수를 제공한다

import { render } from '@testing-library/react';
import App from './App';

describe('Header', () => {
  test('"How it works" link points to the correct page', () => {
    render(
      <MemoryRouter>
        <App />
      </MemoryRouter>
    );
  });
});

이 앱은 create-react-app으로 만들어졌기 때문에 Testing Library 사용에 필요한 모든 것은 이미 설치되어 있다.

어둠 속에서 찔러보지 말라

테스트를 작성하기 시작하면 당신은 마치 어두운 상자 안을 걷는 듯한 느낌을 받을 수 있다. 안에서 어떤 일이 일어나고 있는지 알 수가 없다. 그리고 당신은 브라우저에서 개발자 도구를 열어 DOM 트리를 직접 확인하며 작업하는 일이 익숙해져 있다.

테스트를 작성하기 시작하면 새로운 환경에 적응할 필요가 있다. 테스트 과정에서 무엇이 일어나고 있는지 이해할 방법이 필요하다. 만약 테스트가 어떤 요소를 찾을 수 없어서 실패를 했는데, 원인을 파악할 수 없다면 어떻게 할 것인가?

바로 그런 상황에서 debug 함수로 무척 편리하게 대처할 수 있다. 그것을 사용하면 원하는 실행 지점에서 DOM 트리를 출력할 수 있다. 브라우저의 개발 도구처럼 편리하고 상호작용적이지는 않지만 당신에게 무슨 일이 일어나고 있는지 확실히 파악할 수 있도록 도와준다.

당신은 이제 막 시행착오에 기대지 않는 테스트를 작성하기 시작했다. 그러므로 시간이 조금 걸리겠지만 각각의 단계마다 debug 함수를 사용하자.

import { render, screen } from '@testing-library/react';

describe('Header', () => {
  test('"How it works" link points to the correct page', () => {
    render(
      <MemoryRouter>
        <App />
      </MemoryRouter>
    );
    screen.debug();
  });
});

yarn test 명령어로 테스트를 실행하면 아래의 결과를 보게 될 것이다.

[2020-10-14] 3-debug-app

훌륭하다. 출력에서 몇개의 링크를 포함하는 헤더를 확인할 수 있고, 그것은 우리가 테스트하고 싶은 “How it works” 링크를 포함하고 있다. 이제 우리는 저것에 접근할 수 있는 방법과 조작 방법을 알아야 한다.

렌더링된 DOM 트리에 접근하는 방법

렌더링된 요소 접근에 선호되는 방법은 Testing Library에서 export된 screen 객체를 사용 하는 것이다.

screen 객체는 다양한 쿼리를 제공한다. 그 쿼리들은 DOM에 접근할 수 있는 함수들이다. 아래에 몇개의 예제가 있다.

  • getBy* 쿼리 (ex. getByTestId, getByText, getByRole): 이 함수들은 동기적(synchronous)이며 그 요소가 현재 DOM 안에 있는지 확인한다. 그렇지 않으면 에러를 발생시킨다.
  • findBy* 쿼리 (ex. findByText): 이 함수들은 비동기적(asynchronous)이다. 그 요소를 찾을 때까지 일정 시간(기본 5초)을 기다린다. 만약 그 시간이 지난 후에도 요소를 찾을 수 없으면 에러를 발생시킨다.
  • queryBy* 쿼리: 이 함수들은 getBy* 처럼 동기적이다. 하지만 요소를 찾을 수 없어도 에러를 발생시키지 않는다. 단지 null 값을 리턴한다.

위에서 제시한 쿼리만 해도 요소에 접근할 수 있는 많은 방법을 포함하고 있고, 심지어 Testing Library가 제공하는 함수의 전체 목록도 아니다. 그렇다면 이것들 중에서 “How it works” 링크에 접근하기 위해서는 무엇을 사용해야 할까?

우리는 헤더가 항상 존재한다는 사실을 이미 알고 있다. 표시될 때까지 기다릴 필요가 없다. 이는 우리의 옵션을 getBy* 쿼리로 좁힌다. 하지만 그 중에서 무엇을 골라야 할까?

처음 보기에는 getByTestId가 좋은 선택처럼 보인다. 우리가 해야 할 일은 확인하고 싶은 요소에 테스트 ID를 붙이는 일 뿐이다:

<div data-testid="some-content">
  Some content
</div>

이제 우리는 div 요소에 getByTestId('some-content')를 사용해서 접근할 수 있다. 간단하다, 그렇지 않은가?

하지만 이 방법은 단지 테스트에 통과하기 위해 코드를 수정해야 한다는 말이다. 그다지 이상적이지 않다. 그러니 더 좋은 방법은 없을까?

Testing Library의 문서는 훌륭하며 읽어 볼 가치가 있다. 사실 거기에는 어떤 쿼리를 사용하는 것이 적합한지를 설명하는 문서가 있다.

모든 사람에게 접근 가능한 쿼리는 높은 우선 순위를 가진다. 그 중에서, getByRole이 가장 알맞은 쿼리가 되어야 한다. getByAltText 또는 getByTestId는 오직 예외 상황에서만 사용되어야 한다. 그리고 가장 낮은 우선순위는 getByTestId다. 당신은 반드시 다른 선택의 여지가 없는 상황에서만 테스트 ID를 사용해야 한다.

좋다, 그럼 getByRole을 한번 사용해 보자. 첫번째 파라미터는 대상 요소의 ARIA role이어야 한다. 여기서는 link를 사용할 수 있다. 한 페이지에는 한 개 이상의 링크가 있기 때문에 요소를 찾기 위해name 옵션을 추가할 필요가 있다.

render(
  <MemoryRouter>
    <App />
  </MemoryRouter>
);

const link = screen.getByRole('link', { name: /how it works/i });

여기서 문자열 'How it works' 대신 정규표현식 /how it works/i를 사용했음을 확인하자. 이 방법으로 대소문자가 달라서 요소를 찾지 못하는 경우를 방지할 수 있다(예. CSS text-transformation 속성을 사용해서 대소문자가 바뀌는 경우). 그리고 정규표현식을 사용하면 문자열의 일부를 타겟으로 삼을 수도 있다. /how it/i 정규표현식으로 검색에 성공하겠지만, 'How it' 으로는 실패할 것이다.

파일을 저장하면 테스트가 자동으로 실행된 후 성공했다는 메시지가 뜰 것이다. 이는 우리가 그 링크를 찾았다는 의미다!

우리가 이제 막 시작했기 때문에 모든 것이 예상했던 대로 작동하는지 한번 더 확인하는 편이 좋다. debug 함수를 기억하는가? 많은 개발자는 그것에 파라미터를 넣어도 되는지 모른다. 파라미터에 옵션을 전달해서 콘솔에 1개의 요소만 출력할 수 있다.

const link = screen.getByRole('link', { name: /how it works/i });
screen.debug(link);

아래의 내용을 터미널에서 보게 될 것이다. 출력된 것은 “How it works” 링크. 정확히 우리가 예상했던 대로다.

[2020-10-14] 4-debug-how-it-works-link

DOM 요소와 상호작용 하기

이제 우리는 DOM 요소에 접근하는 방법을 알게 되었다. 구체적으로는 “How it works” 링크다. 하지만 그것으로는 충분하지 않다. 우리가 테스트하고 싶었던 것을 기억하는가?

링크는 정확한 페이지를 가리켜야 한다.

링크가 우리를 타겟으로 이끌기 위해서는 일단 그것을 클릭해야 한다. Testing Library에는 두 가지 옵션이 있다.

  1. @testing-library/react 모듈이 export하는 fireEvent.click 함수를 사용한다.
  2. @testing-library/user-event 모듈이 export하는 click 함수를 사용한다.

가능하다면 @testing-library/user-event를 사용하는 것이 권장된다. 그것은 실제 유저 이벤트에 가까운 더 많은 이벤트(예. 더블클릭)를 제공한다.

그리고 놀라운 사실: package.json 파일을 살펴보면 create-react-app에는 이것이 기본적으로 설치 되어 있다.

그러면 링크를 클릭 해보자.

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';

describe('Header', () => {
  test('"How it works" link points to the correct page', () => {
    render(
      <MemoryRouter>
        <App />
      </MemoryRouter>
    );
    const link = screen.getByRole('link', { name: /how it works/i });
    userEvent.click(link);
  });
});

올바른 페이지가 렌더링 되었는지 확인하기

다음은, 유저가 올바른 페이지로 보내졌는지 확인해야 한다.

그걸 확인할 수 있는 방법 중 하나는 URL을 확인하는 것이다. 그리고 react-router 문서에서 설명하고 있는 방법으로 가능하다. 하지만 유저는 사실 URL에는 신경을 안쓰지 않는가? 그리고 URL이 정확하다 해도 앱은 404 메시지를 표시할 가능성이 있다.

유저가 확인하는 부분은 정확한 페이지를 표시하는지 여부다. 아래의 영상은 브라우저에서 링크를 클릭했을 때 화면이 어떻게 바뀌는지 보여준다.

[2020-10-14] 5-react-testing-intro-link

링크를 클릭하면 “How it works” 제목이 있는 페이지를 나오길 기대한다.

만약 제목이 ARIA role을 가지고 있다면 정확한 페이지가 표시되었는지 확인하기 위해 getByRole 함수를 다시 사용할 수 있다. MDN 문서에 의하면 제목 요소는 heading role을 가진다.

userEvent.click(link);

screen.getByRole('heading', { name: /how it works/i });

이 테스트는 성공한다. 이는 문서 안에 제목이 있다는 의미다. 그리고 또 우리가 정확한 페이지로 이동했다는 것을 의미한다. 아주 훌륭하다!

마지막으로 하나 더: 우리는 어떤 요소가 렌더링 되었는지 확인하는데 getBy* 를 사용하지 말아야 한다. 대신 expect(...).toBeInDocument() 로 확인해야 한다.

아래에서 전체 테스트 코드를 확인할 수 있다.

test('"How it works" link points to the correct page', () => {
  render(
    <MemoryRouter>
      <App />
    </MemoryRouter>
  );

  const link = screen.getByRole('link', { name: /how it works/i });
  userEvent.click(link);

  expect(
    screen.getByRole('heading', { name: /how it works/i })
  ).toBeInTheDocument();
});

테스트는 무척 짧지만 우리가 여기까지 오는 데 많은 시간이 걸렸다. 그리고 그것은 많은 개발자가 처음 테스트를 시작했을 때 느끼는 감정이기도 하다. 하지만 일단 익숙해지면 훨씬 쉽고 빠르게 할 수 있을 것이다.

폼 테스트를 시작하기 전에 하고 싶은 말: 우리는 단지 1개의 링크를 테스트 했을 뿐이다. 당신은 아마 헤더의 왼쪽에 홈 페이지로 이동시키는 로고와 “About” 페이지로 이동시키는 또 다른 링크가 헤더 오른편에 있다는 사실을 알고 있었을 것이다.

그들 두 링크에 대한 테스트는 당신에게 훈련으로 남겨 두려고 한다. 2가지 짧은 힌트를 주자면:

  1. 로고를 감싸고 있는 링크는 getByRole('link', { name }) 으로 테스트할 수 있다. name에 무엇을 넣어야 할지 모르겠다면, screen.debug() 의 결과를 확인해 보길 바란다.
  2. “How it works”, “About” 링크의 테스트는 test.each 함수로 묶을 수 있다.

다음 글: [번역] 초보자를 위한 React 어플리케이션 테스트 심층 가이드 (2)


  1. 알고리즘이 처리하는 데이터의 값이 알고리즘의 특성에 따른 일정한 범위를 넘을 경우에 발생하는 문제. 예를 들어 -128~127 까지의 값을 저장할 수 있는 8비트 변수에 범위를 벗어나는 값을 할당하는 경우 등.

  2. 원문 최하단에서 메일 주소 입력을 통해 받아볼 수 있음

  3. 통합 테스트. 유닛 테스트의 다음 단계로 유닛/컴포넌트를 그룹으로 조합해서 테스트하는 것. 이 테스트의 목적은 컴포넌트가 연결되었을 때 발생하는 문제를 노출시키기 위함이다.

  4. 요구사항 문서에 있는 스펙은 개발자마다 다른 방법을 사용해서 구현할 수 있다. 그렇게 서로 달라질 수 있는 내용을 구현 세부 사항이라고 한다. 구현 세부 사항이 다르면 테스트 방법도 서로 달라야 할 것이다. 예를 들면 배열 정렬을 구현할 때 Array.prototype.sort를 사용할 수도 있고 직접 구현한 알고리즘을 사용할 수도 있다. 중요한 것은 정렬이 제대로 되는지 여부이며, 테스트도 거기에 초점을 맞춰야 한다는 관점.