[react-native] 테스팅 툴 변경 (enzyme -> testing-library/react-native)

2021. 11. 11. 22:56공부내용 공유하기

enzyme 환경에서 비동기 처리 후 state변경에 대응하는 과정에서 너무 고통을 받다 보니, waitFor expectation을 지원하는 testing-library를 써야겠다는 생각이 들었다.

 

전환하고 나니 테스트도 원할해지고, 직관적으로 사용할 수 있는 것 같아 만족스럽다.

 

 

Introduction | Testing Library

React Native Testing Library is a testing library for React Native inspired

testing-library.com

enzyme 다 썰어버리고 심플하게 간다

 

testHelper/utils.tsx

import React from "react";
import {
  fireEvent as fe,
  FireEventFunction,
  render as rtlRender,
} from "@testing-library/react-native";
import { Provider } from "react-redux";
import reduxStore from "@src/redux/store";
import { ReactTestInstance } from "react-test-renderer";

function render(
  ui,
  // @ts-ignore
  { preloadedState, store = reduxStore, ...renderOptions } = {}
) {
  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>;
  }
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
}

// FireEvent Type override -> ReactTestInstance는 null도 리턴함.
type NullableFireEventAPI = FireEventFunction & {
  press: (element: ReactTestInstance | null) => any;
  changeText: (element: ReactTestInstance | null, ...data: Array<any>) => any;
  scroll: (element: ReactTestInstance | null, ...data: Array<any>) => any;
};

const fireEvent: NullableFireEventAPI = fe as any;

// re-export everything
export * from "@testing-library/react-native";
// override render method
export { render, fireEvent };

 

authFlow/index.test.tsx

import { fireEvent, render } from "@src/testHelper/utils";
import makeFlow from "@src/testHelper/makeFlow";
import { AuthFlow } from "@src/navigation";
import React from "react";
import { act, waitFor } from "@testing-library/react-native";
import { ScreenName } from "@src/types/screen";

const DUMMY_SIGNUP_INFO = {
  id: "testID",
  pw: "123123123q",
};

describe("회원가입 플로우 테스트", () => {
  
  it("메인 스크린 컴포넌트 정상 마운트 확인", async () => {
    const { queryByText, toJSON, unmount } = render(
      makeFlow(<AuthFlow />)
    );
    expect(toJSON()).toMatchSnapshot();
    expect(queryByText("회원가입")).toBeDefined();
    expect(queryByText("로그인")).toBeDefined();
    unmount();
  });

  it("회원가입 프로세스", async () => {
    const {
      queryByText,
      queryByTestId,
      queryByPlaceholderText,
      toJSON,
    } = render(
      makeFlow(<AuthFlow initialScreen={ScreenName.SIGN_UP} />)
    );

    expect(toJSON()).toMatchSnapshot();

    fireEvent.changeText(
      queryByPlaceholderText("아이디 입력"),
      DUMMY_SIGNUP_INFO.id
    );
    fireEvent.changeText(
      queryByPlaceholderText("비밀번호 확인"),
      DUMMY_SIGNUP_INFO.pw
    );
    fireEvent.changeText(
      queryByPlaceholderText("영문/숫자/특수문자 혼합하여 8자 이상"),
      DUMMY_SIGNUP_INFO.pw
    );
    
    expect(queryByText("중복확인")?.props.size).toEqual("small");

    await act(async () => {
      fireEvent.press(queryByText("중복확인"));
    });

    await waitFor(
      () => {
        expect(queryByText("중복확인")?.props.active).toEqual(false);
      },
      { timeout: 1500 }
    );

    await waitFor(
      () => {
        expect(queryByText("다음")?.props.active).toEqual(true);
      },
      { timeout: 1500 }
    );

    fireEvent.press(queryByText("다음"));

    expect(toJSON()).toMatchSnapshot();

    // 다음화면 이동 확인
    expect(queryByTestId("next_screen")).toBeDefined();
  });
});

테스트 코드 작성이 너무 쉬워졌다...

 

놀랐던 게 testing library에서는 내가 저번에 만들었던 selector들이 이미 내장되어 있어서 queryByText, queryByTestId 등으로 굉장히 쉽게 각 요소들을 테스트할 수 있었다....

 

참 이런 순간마다 미묘하다.

나 혼자 좋은 아이디어라고 생각하고 구현한 것들이 나중에 알고보니 이미 잘 구현되어 있으면 두 가지 생각이 든다.

하나는 내가 허튼 방식으로 생각하지는 않는구나 하는 안도감, 하나는 내가 생각할 정도면 누군가 이미 생각했구나... 계속 더 창의적인 생각을 해서 남들이 안 하는 걸 찾아봐야겠다는 호승심...

 

뭐 그런 생각은 나중에 하고, 지금은 테스트가 잘 되어서 만족스럽다.

사용자 인터랙션에 가장 밀접한 방식으로 테스트 할 수 있어서 바꾸길 잘했다는 생각이 든다.

 

굿굿