RN을 테스트 진행하는 방법을 따라해 봅시다. 아래에 사용된 코드들은 아래 소스코드 링크에서 확인 할 수 있습니다. 소스코드

  1. 테스트용 프로젝트 만듭니다.
$npx react-native init testingRn
  1. 설치된 패키지를 봅시다.
# package.json
{
  "name": "testingRn",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "17.0.2",
    "react-native": "0.66.3"
  },
  "devDependencies": {
    "@babel/core": "^7.16.0",
    "@babel/runtime": "^7.16.3",
    "@react-native-community/eslint-config": "^3.0.1",
    "babel-jest": "^27.3.1",
    "eslint": "^8.3.0",
    "jest": "^27.3.1",
    "metro-react-native-babel-preset": "^0.66.2",
    "react-test-renderer": "17.0.2"
  },
  "jest": {
    "preset": "react-native"
  }
}

기본적으로 react-native에서 제공하는 jest와 react-test-renderer가 설치되어 있는 것을 확인할 수 있습니다. 기본적으로 해당 툴만 가지고 테스트가 가능하고, 기본적인 테스트 코드는 __TEST__ 폴더에 생성이 되어 있는 것을 확인할 수 있습니다.

image

  1. Jest만으로 테스트가 가능하지만 조금 더 편한게 테스트 하기 위해서 아래 라이브러리를 설치해 봅시다. 이 라이브러리는 Facebook에서 권장하고 있고, 사용자가 컴포넌트를 사용하는 것 처럼 테스트를 작성할 수 있도록 도와주는 툴입니다. 참고 링크
$yarn add -D @testing-library/react-native @types/jest
  1. 간단한 앱을 작성해 봅시다. 아래와 같이 App.js를 작성하고 컴포넌트를 추가합니다. 무스마에서는 Typescript를 사용하는데, 본 테스트 예제에는 그냥 Javascript 문법으로 했습니다.

// App.js
import React from "react";
import { View, Text } from "react-native";
import User from "./src/User";

const App = () => {
  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      <Text>아래에 컴포넌트가 들어갑니다.</Text>
      <User name={"악당별"} id={"a9603"} />
    </View>
  );
};

export default App;

// User.js

import React, { useState } from "react";
import { View, Text } from "react-native";
import CustomButton from "./CustomButton.js";

const User = ({ name, id }) => {
  const [msg, setMsg] = useState("Who a u?");

  return (
    <View>
      <Text>메시지가 표시 됩니다. : {msg}</Text>
      <Text>아이디가 표시 됩니다 : {id}</Text>
      <Text>이름이 표시 됩니다 : {name}</Text>
      <CustomButton
        label="버튼입니다."
        onPress={() => setMsg("버튼을 눌렀습니다.")}
      />
    </View>
  );
};

export default User;
// CustomButton.js

import React from "react";
import { Button } from "react-native";

const CustomButton = ({ label, onPress }) => {
  return <Button title={label} onPress={onPress} />;
};

export default CustomButton;
  1. 앱이 잘 만들어 졌으면 아래 실행 화면과 같이 나옵니다.

image

  1. 기본 제공되는 테스트 소스 코드를 이용해서 앱이 잘 동작하는지 확인해 봅시다.
$yarn test
yarn run v1.22.11
$ jest
 PASS  __tests__/App-test.js
  ✓ renders correctly (3759 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.944 s
Ran all test suites.
✨  Done in 6.74s.
  1. 테스트 결과를 보니 작성한 코드가 잘 동작하는 것 같습니다. 기본 제공된 테스트 파일은 react-test-render를 이용한 방법입니다. 맨 위에 언급한 testing-library를 이용해 봅시다. App-test.js를 아래와 같이 수정해 봅시다.
import "react-native";
import React from "react";
import App from "../App";

// remove origin codes
// Note: test renderer must be required after react-native.
// import renderer from 'react-test-renderer';

// it('renders correctly', () => {
//   renderer.create(<App />);
// });

// added codes
import { render } from "@testing-library/react-native";
let props;
let component;

function getComponent(props) {
  return <App {...props} />;
}

describe("App test...", () => {
  props = {};
  component = getComponent(props);
  test("test 1", () => {
    const rendering = render(component);
    expect(rendering).toMatchSnapshot();
    expect(rendering).toBeTruthy();
  });
});

위 코드에서 describe 는 test를 그룹핑해 주고, test는 실제 테스트를 진행하는 메소드 입니다. (test 말고 it, xtest 등등 있습니다.) component를 render하고 expect를 통해서 어떤 테스트를 해야 하는지 등록하는 것입니다.

expect(rendering).toMatchSnapshot();

위 코드는 스냅샷이 기존 스냅샷과 일치 하는지 확인 합니다.

expect(rendering).toBeTruthy();

위 코드는 null, undefined와 같은 falsy한 값이 있는지 확인합니다.

다시 yarn으로 테스트 해 봅시다.

$yarn test
yarn run v1.22.11
$ jest
 PASS  __tests__/App-test.js
  App test...
    ✓ test 1 (390 ms)

 › 1 snapshot written.
Snapshot Summary
 › 1 snapshot written from 1 test suite.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   1 written, 1 total
Time:        1.203 s, estimated 5 s
Ran all test suites.
✨  Done in 2.63s.

테스트가 예쁘게 잘 됩니다.

  1. 테스트 결과는 __tests__ 폴더 아래에 __snapshots__ 라는 폴더에 보면 됩니다. 스냅샷 테스팅은 UI가 예측 못하게 바뀌는 것을 방지할 때 사용합니다. 만약 컴포넌트나 엘레먼트 등이 변경 되면 스냅샷을 다시 찍어야 합니다.
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`App test... test 1 1`] = `
<View
  style={
    Object {
      "alignItems": "center",
      "flex": 1,
      "justifyContent": "center",
    }
  }
>
  <Text>
    아래에 컴포넌트가 들어갑니다.
  </Text>
  <View>
    <Text>
      메시지가 표시 됩니다. :
      Who a u?
    </Text>
    <Text>
      아이디가 표시 됩니다 :
      a9603
    </Text>
    <Text>
      이름이 표시 됩니다 :
      악당별
    </Text>
    <View
      accessibilityRole="button"
      accessible={true}
      collapsable={false}
      focusable={true}
      nativeID="animatedComponent"
      onClick={[Function]}
      onResponderGrant={[Function]}
      onResponderMove={[Function]}
      onResponderRelease={[Function]}
      onResponderTerminate={[Function]}
      onResponderTerminationRequest={[Function]}
      onStartShouldSetResponder={[Function]}
      style={
        Object {
          "opacity": 1,
        }
      }
    >
      <View
        style={
          Array [
            Object {},
          ]
        }
      >
        <Text
          style={
            Array [
              Object {
                "color": "#007AFF",
                "fontSize": 18,
                "margin": 8,
                "textAlign": "center",
              },
            ]
          }
        >
          버튼입니다.
        </Text>
      </View>
    </View>
  </View>
</View>
`;
  1. 이번에는 CustomButton 컴포넌트에 대한 단위 테스트를 해 봅시다. 위에서 사용했던 describe와 test를 이용합니다. 아래 파일을 생성하고 테스트 봅시다.
// CustomButton-test.js

import React from "react";
import { render, fireEvent } from "@testing-library/react-native";
import CustomButton from "../src/CustomButton";

describe("Custom Button test", () => {
  const onPressMockup = jest.fn();
  const props = {
    label: "라벨입니다.",
    onPress: onPressMockup,
  };

  test("onPress test", () => {
    const rendering = render(<CustomButton {...props} />);
    var TEST_COUNT = 100;
    for (let i = 0; i < TEST_COUNT; i++) {
      fireEvent.press(rendering.getByText("라벨입니다."));
    }
    expect(onPressMockup).toBeCalledTimes(TEST_COUNT);
  });
});
$yarn test
yarn run v1.22.11
$ jest
 PASS  __tests__/App-test.js
 PASS  __tests__/CustomButton-test.js

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   1 passed, 1 total
Time:        1.313 s, estimated 2 s
Ran all test suites.
✨  Done in 2.00s.
  • jest.fn()은 mocking할 때 사용합니다. 실제 구현 된 함수를 Mock(모의) 함수로 대체하고 코드 간의 상호 작용을 테스트할 때 사용합니다. 이 부분은 추후 포스팅에서 자세히 다루겠습니다.
  • fireEvent()는 onPress, onChange 등 특정 이벤트에 대한 상호작용을 확인할 때 사용합니다. 위 예시에서 rendering 된 화면에 ‘라벨입니다.’라는 버튼을 찾아서 onPress를 동작 시킵니다. 그 후 expect(onPressMockup)을 만나서 테스트를 진행합니다. ‘라벨입니다.’라는 컴포넌트가 없거나, onPress를 할 수 없다면 test fail이 됩니다.
const props = {
  label: "라벨입니다.",
  onPress: onPressMockup,
};

이 코드를

const props = {
  label: "라벨입니다.",
  // onPress: onPressMockup,
};

으로 변경한 다음 test를 진행하면 onPress했을 때, onPressMockup을 수행하지 않았기 때문에 expect(onPressMockup) 테스트 항목에서 fail이 됩니다.

$yarn test
yarn run v1.22.11
$ jest
 FAIL  __tests__/CustomButton-test.js
  ● Custom Button test › onPress test

    expect(jest.fn()).toBeCalledTimes(expected)

    Expected number of calls: 100
    Received number of calls: 0

      15 |             fireEvent.press(rendering.getByText('라벨입니다.'))
      16 |         }
    > 17 |         expect(onPressMockup).toBeCalledTimes(TEST_COUNT)
         |                               ^
      18 |
      19 |     })
      20 | })

      at Object.<anonymous> (__tests__/CustomButton-test.js:17:31)

 PASS  __tests__/App-test.js

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   1 passed, 1 total
Time:        1.182 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
  1. 이번 블로그에서는 테스트를 진행하는 방법과 간략한 테스트를 진행했습니다. 다음 파트에서는 jest.fn 과 더 다양한 테스트 방법을 알아 보도록 하겠습니다.