
최소 단위인 유닛이 정확하게 동작하는지 확인하는 것이다.
이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 확인하고 수정할 수 있다.
유닛 테스트는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차다.
즉, 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차를 말한다.
react-testing-library는 Jest의 대체품이 아니고, 리액트 컴포넌트 테스트를 하려면 둘 다 필요하다.
Jest 같은 경우는 test runner이다.(테스트를 실행하도록 해준다.) 테스트를 찾아서 실행하고 해당 테스트가 통과일지 실패일지를 결정한다.
react-testing-library는 리액트 컴포넌트 testing을 위한 가상 돔(Virtual DOM)을 제공해준다.
(1) Jest란?
Jest란 코드가 제대로 동작하는지 확인하는 Test Case 를 만드는 페이스북이 개발한 'JS 테스팅 프레임워크'이다.
(2) RTL이란?
React Testing Library(이하 RTL)는 구현 기반의 테스트 도구인 Enzyme의 대안으로 자리 잡은 테스트 도구입니다. 따라서 RTL은 세부적인 구현사항보다는 실제 사용자 경험과 유사한 방식의 테스트를 작성할 것을 권고합니다.
yarn을 이용해서 Test에 필요한 모듈 설치
'@testing-library/react';
'@testing-library/jest-dom';
'jest-styled-components';
@testing-library/user-event;
jest
package.json
"jest": "react-scripts test",
./src/jest/*.test.js 파일을 만듭니다.
$yarn jest
기본적으로 Test.js 파일에서 Test 하려는 파일과 환경 구성을 같게 해줘야 Test가 정상 작동합니다.
테스트 코드
Arrange : 테스트 데이터와 테스트 조건 및 환경 설정을 준비
Act : 테스트해야 하는 로직 실행
Assets : 예상한 결과와 실행 결과를 비교
파일 생성 위치 : ./src/jest/*.test.js 에 위치합니다.
Test하려는 page 또는 Components의 환경을 같게 해서 test가 돌아갈 수 있도록 한다.
테스트하려는 page 또는 Components를 Import합니다.
필요한 Module을 Import
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import 'jest-styled-components';
import userEvent from '@testing-library/user-event';
import { ThemeProvider } from 'styled-components';
import theme from '../styles/Theme';
import Signin from '../pages/SignIn';
Mocking이 필요 여부를 확인합니다.
const mockedUsedNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockedUsedNavigate,
// SHORT: jest.fn(),
}));
describe을 구성합니다.
itdescribe 안에서 세부 적인 구현 사항을 it 메소드를 통해 구현합니다.
describe('signIn test', () => {
it('should render signIn', () => {
render(
);
});
테스트한 코드가 어떤 결과가 나와야 하는 것인지 확인합니다.
expect(inputTitle).toBeInTheDocument();
expect(idInputBoxTitle).toBeInTheDocument();
expect(screen.getAllByText('로그인')[0]).toBeInTheDocument();
expect(signUp).toBeInTheDocument();
expect(changePassword).toBeInTheDocument();
expect(button).toBeInTheDocument();
필요한 데이터가 없으면 컴포넌트 렌더링이 되지 않는 문제를 해결하기 위해 mock 데이터가 필요하다.
const staffDetailData = {
authority: 'staff',
code: 'ax71708432_587848',
full_name: 'KimYoungseo',
nickname: 'Zero',
job_name: 'developer',
email: 'zero@axchange.co',
phone_number: '010-5234-4037',
company: {
code: 'ax71708432',
calling_company_name: 'ax',
},
};
render(
);
필요한 데이터를 객체 형태로 만들어주고 render 할 때 props로 넘겨준다.
import axios from 'axios';
jest. Mock('axios');
const WarningData = {
title_1: '님의 계정을',
title_2: '정말로 삭제하시겠습니까?',
subTitle_1: '계정을 삭제할 시, 본 계정의 소유자는',
subTitle_2: '탈퇴 진행되며 삭제된 계정은',
subTitle_3: '복구되지 않습니다.',
btn_1: '삭제하기',
};
describe('StaffListModal test', () => {
it('should render StaffListModal', async () => {
axios.get.mockResolvedValue({ data: WarningData });
render(
);
const email = await waitFor(() => screen.getByText('이메일'));
});
});
jest/___.test.js 파일에서 axios를 import 한다.
WarningData 목데이터를 만들고 axios.get.mockResolvedValue({ data: WarningData }); 을 통해 데이터를 컴포넌트에서 불러온다.
await waitFor(() => screen.getByText('이메일')); await waitFor을 통해서 컴포넌트가 렌더링 될 때까지 기다린다.
테스트를 실행 시 실제 백앤드 API에 네트워크 호출을 하는 대신에 훨씬 빠르고 안정적인 가짜 API 서버를 구축하기 위해서, 필요한 Mock Data을 활용해 Unittest를 진행한다.
특징
유연하게 디자인이 되어 있어서 개발용으로 브라우저 환경에서 서비스 워커로 돌리든 테스트용으로 Node.js 환경에서 Jest나 Cypress와 같은 테스트 러너(runner)로 돌리든 동일한 요청 핸들러(handler) 코드를 공유해서 사용할 수 있다는 것입니다. 이 말은 API 모킹을 위해서 작성해야하는 코드를 최소화 할 수 있다는 뜻이며 역시 개발 생산성 뿐만 아니라 유지 보수성 측면에서도 긍정적인 효과를 가져올 것입니다.
REST API 모킹과 GraphQL API 모킹을 모두 지원한다
단점
소소한 단점이라고 한다면 내부적으로 서비스 워커를 사용하다보니 인터넷 익스플로러와 같이 서비스 워커를 지원하지 않는 구식 브라우저에서는 동작하지 않는다
적용 방법
handles.js , server.js 파일 생성
server.js (jest 테스트를 위한 서버 생성)
msw/node 모듈의 setupServer() 함수를 이용하여 테스트용 API 서버를 만든다.
setupTest.js 에서 Jest server test를 설정한다.
handles.js
rest 함수를 통해 restfull Api 요청 테스트
상세 코드 : https://github.com/axchange/ax-cloud-web-v4/pull/163 - Github 계정 연결
쿼리 함수들은 react-testing-library 의 기반인 dom-testing-library 에서 지원하는 함수
쿼리 함수들은 Variant 와 Queries 의 조합
(1) Variant
getBy
getBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나를 선택합니다. 만약에 없으면 에러가 발생합니다.
getAllBy
getAllBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개를 선택합니다. 만약에 하나도 없으면 에러가 발생합니다.
queryBy
queryBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나를 선택합니다. 만약에 존재하지 않아도 에러가 발생하지 않습니다.
queryAllBy
queryAllBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개를 선택합니다. 만약에 존재하지 않아도 에러가 발생하지 않습니다.
findBy
findBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 하나가 나타날 때 까지 기다렸다가 해당 DOM 을 선택하는 Promise 를 반환합니다. 기본 timeout 인 4500ms 이후에도 나타나지 않으면 에러가 발생합니다.
findAllBy
findBy* 로 시작하는 쿼리는 조건에 일치하는 DOM 엘리먼트 여러개가 나타날 때 까지 기다렸다가 해당 DOM 을 선택하는 Promise 를 반환합니다. 기본 timeout 인 4500ms 이후에도 나타나지 않으면 에러가 발생합니다.
(2) Queries
ByLabelText
ByLabelText 는 label 이 있는 input 의 label 내용으로 input 을 선택합니다.
ByPlaceholderText
ByPlaceholderText 는 placeholder 값으로 input 및 textarea 를 선택합니다.
ByText
ByText는 엘리먼트가 가지고 있는 텍스트 값으로 DOM 을 선택합니다.
정규식을 넣어도 작동함
ByAltText
ByAltText 는 alt 속성을 가지고 있는 엘리먼트 (주로 img) 를 선택합니다.
ByTitle
ByTitle 은 title 속성을 가지고 있는 DOM 혹은 title 엘리먼트를 지니고있는 SVG 를 선택 할 때 사용합니다.
ByTestId
ByTestId 는 다른 방법으로 못 선택할때 사용하는 방법인데요, 특정 DOM 에 직접 test 할 때 사용할 id 를 달아서 선택하는 것을 의미합니다.
(3) 쿼리 사용 권장
getByLabelText
getByPlaceholderText
getByText
getByDisplayValue
getByAltText
getByTitle
getByRole
getByTestId
(1) toEqual(), toBe()
toEqual() - 객체 인스턴스의 모든 속성을 재귀적으로 비교하는 데 사용 ( 객체 내용이 같은지 판단)
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
toBe() - 객체의 내용이 같더라도 데이터가 속한 메모리 객체까지 판단. (정확한 값 일치 여부 확인)
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
(2) toBeTruthy(), toBeFalsy()
js는 자바와 다르게 boolean 타입에 한정되지 않는다.
1이 true이고 0이 false로 간주되는 것과 같이 모든 타입 값들을 true, false로 간주하는 규칙이 있다.
(3) toContain()
Array 또는 iteration이 가능한 (Set, Map 등...) 객체에 특정 요소 포함 여부를 확인
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'beer',
];
test('the shopping list has beer on it', () => {
expect(shoppingList).toContain('beer');
expect(new Set(shoppingList)).toContain('beer');
});
(4) toMatch()
정규식을 이용해서 문자열의 일치 여부 확인
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
test('but there is a "stop" in Christoph', () => {
expect('Christoph').toMatch(/stop/);
});
(5) toThrow()
함수 호출 시 에러 발생 여부 확인
단순 에러 및 특정 에러 지정 가능
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow();
expect(compileAndroidCode).toThrow(Error);
// You can also use the exact error message or a regexp
expect(compileAndroidCode).toThrow('you are using the wrong JDK');
expect(compileAndroidCode).toThrow(/JDK/);
});
참조
벨로퍼트와 함께하는 리액트 테스팅
Testing React Apps · Jest
Jest 사용법 (2) - 설치
Jest로 기본적인 테스트 작성하기
Jest
JEST 사용법
Unit Testing of React Apps using JEST : Tutorial | BrowserStack
테스트 코드 작성하면서 참고한 사이트
react-testing-library 를 사용한 리액트 컴포넌트 테스트
react testing library를 사용할때 주의할점
React Testing Library 사용법
유저 이벤트 테스트 (@testing-library/user-event)
How to access child element from parent using react testing library?
TDD & RTL
react-testing-library의 “not wrapped in act” Errors 원인과 해결법
Async Methods | Testing Library
How to test url change with Jest
react test code.01 테스트 대상 찾고 테스트하기
jest not implemented window.alert()
About Queries | Testing Library
Web: 모듈, API Mocking하기(jest, axios-mock-adapter)
React Testing Library을 사용하여 테스트2 - Queries
ReactRouteDom err 참고
react-router-dom useHistory 함수 mocking하기
testing-library/react (테스트 코드가 줄어들고, 사용자 입장에서의 테스트가 가능)
https://blog.mathpresso.com/모던-프론트엔드-테스트-전략-2편-de069e271b3d
테스트 도구 설명
test-library-framework/todoList.test.tsx at master · qkreltms/test-library-framework
Mocking 관련
210505 RTL(React Testing Library)를 활용한 테스트 (+Axios Mockup 테스트)