testing-library로 페이지 테스트 작성하기

김수지·2020년 12월 7일
0
post-thumbnail

Today What I Learned

매일 배운 것을 이해한만큼 정리해봅니다.
오늘은 리액트에서 tesing-library를 통해 페이지 렌더 테스트를 만드는 과정을 공부해보았습니다.


오랜만에 회사에서 페어 프로그래밍을 했는데 오늘은 웹페이지를 ts로 변경하고, 페이지 렌더 테스트까지 작성했다. 유닛 테스트는 작성해보았지만 페이지 렌더 쪽 테스트 작성은 이번이 처음이라(ts도 아직 모지리지만) 페어분이 몇가지 포인트로 알려주신 테스트 작성 방식과 고려점을 좀 정리해보려고 한다.

블로깅에 앞서 Special Thanks to today's pair, Moon.

1. Testing Library

1. Testing Library이란?

Simple and complete testing utilities that encourage good testing practices

  • 좋은 테스팅 습관을 길려주는 간단하고 완벽한 테스팅 유틸리티
  • 오늘도 시작은 홈페이지에 definition부터, 이들이 말하는 좋은 테스팅 습관은 무엇일까?
    테스트 방법론의 종류는 매우 다양하지만 오늘 언급한 테스팅 라이브러리는 아래 문제를 해결하고자 존재한다.

2. When to User Testing Library

  • ui 개발 중 마주치게 되는 문제들
    1. 개발을 하다보면 비즈니스 로직 뿐 아니라 UI를 테스트하고 싶을 때가 있다. 그러나 UI가 담는 모든 디테일을 실어 나르면서 테스트하는 방법은 피하고 싶다. 의도한 지점들을 간파하는 테스트만 만들어 내는 방법은 없을까?
    2. 기존 개발에 변화를 줄 수 있는 요소들이 있을 것이다. 이런 요소들이 이미 정성 들여 작성해둔 테스트 코드를 망가트리지 않았으면 한다. 또 이런 요소들 때문에 개발자의 개발 속도를 잡아먹지 않았으면 한다.. 이런 점들을 감안해서 유지보수 가능한 코드를 작성할 순 없을까?
  • 위 문제를 해결하기 위해선 테스팅 라이브러리는 개발한 코드를 구현하기 위한 모든 필요 요소를 갖추지 않고도 매우 가볍게 테스트를 작성할 수 있게 만들어졌다. 유저들이 어플리케이션에서 ui를 발견할 수 있는 것처럼 노드구조에서 무언가를 불러와서(query) 발견하는 구조로 작성되었다고 한다.

2. Testing Library로 Page 테스트 작성하기

  • 프론트엔드에서도 생각보다 다양한 테스트를 구현할 수 있다.
    • 유닛테스트로 클라이언트에 존재하는 비즈니스 로직을 테스트할 수도 있고
    • 스토리북과 같은 툴을 사용해서 컴포넌트 단위로 ui/ux를 고려하며 테스트할 수 있다.
    • 또 사이프레스와 같은 툴을 사용해 실제 사용자가 클라이언트를 다루는 것처럼 플로우를 작성하여 e2e 테스트를 진행해볼 수도 있다.
    • 혹은 페이지를 단위로 하는 컴포넌트 렌더 테스트도 필요할 수 있다.
  • 오늘 내가 페어프로그래밍 한 것도 마지막 맥락이었는데, 테스팅 라이브러리가 제공하는 툴들을 이용해서 리액트 페이지 렌더와 페이지 내 유의미한 액션이 기대한 바대로 작동하는지를 테스트하는 방법을 좀 더 살펴보겠다.

1. 테스팅 기본 개념

  • 테스트 시에는 의존적인 상황이 아니라 고립된 상황에서의 구현을 확인해 볼 필요가 있다. 그래서 페이지 혹은 컴포넌트에서 작성한 method나 util 함수를 대신하여 테스트 오브젝트를 생성하여 활용하곤 하는데 이 때 Stub과 Mock이라는 개념을 적용한다.
  • Stub과 Mock은 모두 dummy obejct를 반환한다. 그러나 아래와 같은 차이점이 있다.
  • Stub: 어떠한 비즈니스 로직에 기반하여 항상 정해진 결과를 리턴하도록 object를 짜는 방법, 즉 상황에 따라 어떤 로직에 기반해 항상 정해진 더미 객체를 반환하는 형태이다.
  • Mock: 모킹 또한 정해진 결과를 항상 리턴하지만, 사용자 임의의로 액션을 조작할 수 있다. 예를 들어 성인인지를 확인하는 method를 앞두고 "15세 혹은 20세" 등의 더미값을 리턴하는 식이라고 볼 수 있다. Mock은 Stub과 대조되는 개념이 아니라 좀 더 상위의 개념이라고 볼 수 있다.
  • 좀 더 자세한 내용을 보려면 quora의 질의응답을 봐도 좋을 것 같다.

2. 페이지 테스트 절차와 고려점

  • 나는 이번에 이미 작성되어 있는 페이지의 테스트 코드를 작성하였기 때문에 기존 페이지 컴포넌트를 대조하면서 테스트를 작성하였다. 궁극적으로는 기본적인 <div>Page </div> 기본 구조부터 테스트와 함께 작성하는 것을 목표로 하면 좋을 것 같다.

1. 무엇부터 작성할 것인가

  1. 페이지 외부에서 주입하는 것들을 파악: Provider, props
    • 먼저 페이지를 그리기 위해 페이지 외부에서 받아와야 하는 것을 정의하고 구현한다.
    • 페이지 외부에서 받아오는 것들은 대표적으로 Provider로 주입되는 정보들과 props들이 있을 것이다.
    • redux, mobx 같은 state management를 사용한다면 이 라이브러리들이 HOF 개념으로 컴포넌트에 store 정보를 주는 것을 알 수 있다. 이번 테스트에서는 단독으로 페이지를 그리기 때문에 페이지 바로 상단에 Provider로 정보를 주입해주어야만 한다.
    • 나 같은 경우는 페이지 컴포넌트에서 사용하는 mobx store 일부를 찾고 해당 store에서 사용하는 value property와 method를 정의하였다.
    • 아래와 같은 컴포넌트에서는 loading, route와 같은 props와 sessionManager, userManager 등의 store 주입이 있다.
// @ MyPage Component
import React from "react";
import { Redirect } from "react-route-dom";
import { useStores } from "../utils/hooks";
import { Photo } from "./Photo";
import { Loading } from "../common/Loading";

const MyPage: React.FC = ({loading, route: {disableFooter}}) => {
  const {sessionManager, userManager } = useStores();
  
  ...
  const handleFetchPhotos = id => {
    return userManager.fetchPhoto().then(({id}) => {
      
    });
  }
  
  if(!sessionManager.signIn) {
    return <Redirect to="/home" />;
  }
  
  if(loading) {
    return <Loading />
  }
    
  return (
    <div data-test="my_page">
      <Photo onFetchPhotos={handleFetchPhotos}/>
    </div>
    );
}
  export default MyPage;
  1. 주입한 store와 props에서 컴포넌트에 사용되는 프로퍼티나 메소드를 stub/mock의 개념을 빌어 더미로 구현한다.
    • 앞서 테스팅 라이브러리에서 언급한 해결점 중 하나가 ui 테스트 상에서 필요로 하는 모든 정보를 구현하지 않은 채 기능하는지를 체크하는 테스트를 작성하게 하는 것!이었다.
    • 이를 위해 페이지가 활용하고 있는 모든 값을 다 넣지 않고도 faker와 같은 툴을 이용해 dummy data를 간단하게 만든다.
    • 대부분 이러한 행위는 test/it을 사용하여 단위별 테스트를 작성하기 전인 beforeEach 단계에서 작성하고, 각 테스트가 마주하는 상황에 독립성을 확보해주어야 한다.
    • beforeEach 단계에서의 작성 방식은 단순 모킹으로 구현하여도 되고, msw와 같은 npm 라이브러리를 사용할 수도 있다.
    • 테스트이긴 하지만 실제로 서버로 api 통신을 통해서 값을 받아오는 과정이 존재할 수도 있기 때문에 msw를 활용하여 클라이언트 -> 서버 간 통신 사이 프록시 서버를 만들고 서버단의 api response를 모킹하여서 클라이언트로 돌려주는 식으로 테스트를 작성할 수도 있다.
    • 아래와 같은 식으로 스텁을 구현하고 리액트 테스트 라이브러리에서 제공하는 render, configure 등의 메소드를 가져와서 ui 상의 변경을 테스트로 정의한다.
// @ MyPageComponent.spec.js
import faker from "faker";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { render, configure } from "@testing-library/react";

...
describe("My Page Test", () => {
  const route = {
    disableFooter: false,
  };
  const server = SetupServer();
  let stores = {};
  
  beforeEach(() => {
    server.listen();
    sotres = {
      sessionManager: {
        signedIn: true,
      },
      userManager: {
        fetchUserInfo: () => ({
          id: faker.random.number();
          userCode : faker.random.word();
        }),
        fetchPhoto: ({id: "12345"}) =>  Promise.reolve();
      }
    };
  });

  afterEach(() => {
    jest.clearAllMocks();
  });
  afterAll(() => {
    server.close();
  });

  it("페이지를 렌더한다", async() => {
    const {debug, getByTestId} = render(<Provider {...stores}/><MyPage route={route}/></Provider>);
     });
    debug();
    expect(queryByTestId("my_page")).toBeInTheDocument();
  });

  function SetupServer() {
    return setupServer(
      rest.get("/api/myInfo.json", (req, res, ctx) => {
        return res(ctx.json({userCode: "1234qwerasdf"}));
      }),
    );
  }

}
  1. 그런 다음 렌더 후 페이지 내 유의미한 행동들이 실행되고 있는지에 대한 테스트 문을 짠다. 여기서부터는 유닛 테스트와 크게 다르지 않고, ui 단의 변경까지 테스트에서 감지하고 싶은 경우 위에서와 같이 getText 등으로 ui 상 변경점을 expect문으로 체크하면서 테스트를 작성하면 된다.
    (이미 글이 길어져서 이 뒤 테스트 작성 방식은 향후 포스트에서 예시를 적어보는 것으로)

2. 페이지 테스트 작성 시 주의점

  • 어떤 컴포넌트도 그렇겠지만 특히 페이지 테스트의 경우 무한 루프를 돌게하는 로직이 없는지 체크해야 한다.
  • 위에서 작성한 것처럼 페이지를 구현하기 위한 더미 데이터를 이용해 먼저 1)렌더 시키고 2)렌더 된 형태에서 특정 유의미한 기능이 작동하는지를 확인하는 것이 테스트의 목적이다.
  • 이 때 1)이 완료되지 않고 계속해서 무한루프에 빠져들면 테스트의 목적을 잃게 되기 때문이다.
  • 따라서 render 단의 조건절이 테스트에서도 잘 구현되어 있는지를 보아야 한다.
profile
선한 변화와 사회적 가치를 만들고 싶은 체인지 메이커+개발자입니다.

0개의 댓글