이 글은 아래 글에서 소개했던 직접 만든 리액트 전역상태관리 라이브러리인 @yogjin/react-global-state-hook 의 테스트 코드 작성 정리 글입니다.
1. 테스트 코드를 작성하면 좋은 점
테스트 코드를 추가하면
- 마음놓고 코드 변경을 할 수 있다. (잘못된 동작을 하면 테스트코드가 깨지므로 바로 알아챌 수 있다)
- github actions로 PR을 날렸을 때 테스트가 돌아가도록 자동화하여 테스트가 깨진다면 merge되지 못하도록 막을 수 있다.
이 장점들을 누리기위해 @yogjin/react-global-state-hook 에 테스트 코드를 추가하려고한다.
2. 어떤 테스트 툴을 이용해야할까?
테스트 하려는 코드는 리액트 전역상태관리 라이브러리이다.
즉 우리가 검증해야할 부분은
- 상태관리 로직(함수)에 문제가 없는지
- 리액트 환경에서 잘 돌아가는지
를 검증해야한다.
1번은 단위테스트를 해야하고 2번은 유저의 입장에서 테스트해도 괜찮을 것 같았다.
그래서 jest + react-testing-library 조합으로 테스트하기로 했다.
3. 테스트 환경설정
jest는 es6, typescript, react 코드를 이해하지 못하므로 테스트코드를 babel을 이용해서 트랜스파일링 해야한다.
package.json, babel.config.json, jest.config.json 코드와 설명을 주석으로 달아놓았다.
package.json
{
...
"scripts": {
"prepack": "yarn build",
"build": "yarn tsc",
"test": "yarn jest", // jest 실행 스크립트
"test:watch": "yarn test --watch"
},
"devDependencies": {
"@babel/core": "^7.22.11", // babel 필수
"@babel/preset-env": "^7.22.10", // 설정한 환경에 따라 필요한 plugin 제공 preset
"@babel/preset-react": "^7.22.5", // react preset
"@babel/preset-typescript": "^7.22.11", // typescript preset
"@testing-library/jest-dom": "^6.1.2", // Jest에 대한 커스텀 DOM 요소 매칭 함수(toBeInTheDocument()등) 제공
"@testing-library/react": "^14.0.0", // render, screen 등 함수제공: react dom을 render하고, screen으로 render된 dom에 접근할 수 있음
"@testing-library/user-event": "^14.4.3", // "@testing-library/react" 이 제공하는 fireEvent 보다 더 유저 친화적으로 동작하는 함수 제공
"@types/jest": "^29.5.4",
"@types/react": "^18.2.21",
"jest": "^29.6.4",
"jest-environment-jsdom": "^29.6.4", // jest가 jsdom환경에서 돌아가도록 하려면 필수로 설치
"typescript": "^5.1.6"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0" // 테스트 환경에서만 쓰이므로 devDependecies로 옮겨도 무방
}
}
babel.config.json
{
"presets": [
["@babel/preset-env", { "targets": { "node": "current" } }], // jest테스트는 node환경에서 돌아가므로
["@babel/preset-react", { "runtime": "automatic" }], // automatic: jsx를 transpile하는 함수들을 자동으로 import.
"@babel/preset-typescript"
]
}
"@babel/preset-react" 에서 { "runtime": "automatic" } 설정을 해줘도
typescript 이용하는 테스트 코드에서는 타임검사때문에 어쩔수없이 import React from 'react' 를 해줘야한다.
(컴파일타임에는 나중에 자동으로 함수가 import 될 거라는 사실을 모르기 때문에)
jest.config.json
{
"testEnvironment": "jsdom", // 테스트를 실행할 환경, 가상 dom 환경을 사용
"verbose": true, // 개별 테스트 결과 출력
"collectCoverage": true // 테스트 코드 커버리지 수집, 결과파일 생성
}
4. 작성한 테스트 코드
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { globalState, useSetGlobalState } from '../src/index';
import Component1 from './src/components/Component1';
import Component2 from './src/components/Component2';
test('non-react 코드에서 global state 저장이 잘 된다.', () => {
const state = globalState(1);
const setState = useSetGlobalState(state);
setState(2);
expect(state.getState()).toEqual(2);
});
test('컴포넌트들끼리 glabalState 공유가 잘 된다.', async () => {
render(
<div>
<Component1 />
<Component2 />
</div>
);
userEvent.click(screen.getByText('Add Count'));
userEvent.click(screen.getByText('Add Count'));
const countsOf2 = await screen.findAllByText('2');
expect(countsOf2).toHaveLength(2);
const countsOf3 = await screen.findAllByText('3');
expect(countsOf3).toHaveLength(2);
});
첫번째 test에서는 실제 set 로직이 잘 돌아가는지 테스트했다.
또한 리액트 환경이 아닐때도 잘 돌아가는것을 추가로 확인할 수 있다.
두번째 test는 리액트 환경에서도 잘 돌아가는지 테스트하기 위해
내부적으로 라이브러리를 이용하는 Component1, Component2 컴포넌트를 만들어서 렌더링했다.
Add Count를 두번해서 생긴 2와 3이 2개씩 존재하는지 확인함으로써
라이브러리가 잘 동작하는지 검증했다.
5. github actions를 이용해 테스트자동화
PR을 날릴 때마다 해당 PR으로 인해 변경된 라이브러리 코드가 문제없는지 확인하기 위해 테스트 자동화가 필요했다.
.github/test.yml 에 간단히 작성해보았다.
name: jest test
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: yarn
- name: Run tests
run: yarn test
이제보니 라이브러리 캐싱을 통해 CI를 조금 더 빠르게 돌리려면
Install dependencies의 yarn을 yarn install --frozen-lockfile 로 바꾸면 더 좋을 것 같다
6. 마치며
단순히 리액트 전역상태관리를 하려고만 했는데
욕심이 많아져서 생각보다 더 많은 일을 했다.. ㅋㅋㅋ
npm 배포, 테스트코드와 자동화까지..
또 포스팅하진 않았지만 이왕 라이브러리 소개와 사용법을 적은 README를 추가하고 MIT LICENSE 파일도 추가해줬다.
npm 배포가 처음은 아니고, 우테코 레벨2 때 modal을 배포해본 경험이 있긴하다.
하지만 시간에 쫓겨 급하게 한 감이 있어서 다시한번 해보고 싶었다.
테스트코드도 동글에서는 cypress를 이용했기 때문에, jest + rtl 조합이 어떤 느낌인지 경험해보고 싶었다.
actions 자동화도 해두면 언제나 좋기 때문에 진행했다.
github package와 npm을 동기화해서 npm 배포도 자동화하고 싶었는데,,
며칠동안 삽질만 하고 어떻게 해야할 지 아직 잘 모르겠다😭
하나에 매몰되어있어 조금 지쳤기도하고, 다른 공부도 해보고 싶은 게 많기 때문에
나중에 또 기회가 되면 다시 도전할 생각이다.
'우아한테크코스 5기 프론트엔드' 카테고리의 다른 글
[프로젝트 동글] 웹 접근성 관리 대상 선정 및 개선 (0) | 2023.08.29 |
---|---|
[프로젝트 동글] 근거있는 Typescript 컨벤션 (0) | 2023.08.28 |
React18의 useSyncExternalStore 훅으로 전역상태 관리하기 (0) | 2023.08.24 |
[우아한테크코스 프로젝트 기록] 3차 데모데이 ~ 론칭 페스티벌 (5) | 2023.08.22 |
[우아한테크코스 프로젝트 기록] 2차 데모데이 ~ 3차 데모데이 (1) | 2023.08.05 |