React-native for testing by testing-library (part 1)
배석현 책임
RN을 테스트 진행하는 방법을 따라해 봅시다. 아래에 사용된 코드들은 아래 소스코드 링크에서 확인 할 수 있습니다. 소스코드
- 테스트용 프로젝트 만듭니다.
$npx react-native init testingRn
- 설치된 패키지를 봅시다.
# 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__ 폴더에 생성이 되어 있는 것을 확인할 수 있습니다.
- Jest만으로 테스트가 가능하지만 조금 더 편한게 테스트 하기 위해서 아래 라이브러리를 설치해 봅시다. 이 라이브러리는 Facebook에서 권장하고 있고, 사용자가 컴포넌트를 사용하는 것 처럼 테스트를 작성할 수 있도록 도와주는 툴입니다. 참고 링크
$yarn add -D @testing-library/react-native @types/jest
- 간단한 앱을 작성해 봅시다. 아래와 같이 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;
- 앱이 잘 만들어 졌으면 아래 실행 화면과 같이 나옵니다.
- 기본 제공되는 테스트 소스 코드를 이용해서 앱이 잘 동작하는지 확인해 봅시다.
$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.
- 테스트 결과를 보니 작성한 코드가 잘 동작하는 것 같습니다. 기본 제공된 테스트 파일은 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.
테스트가 예쁘게 잘 됩니다.
- 테스트 결과는 __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>
`;
- 이번에는 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.
- 이번 블로그에서는 테스트를 진행하는 방법과 간략한 테스트를 진행했습니다. 다음 파트에서는 jest.fn 과 더 다양한 테스트 방법을 알아 보도록 하겠습니다.