저자는 Vets Who Code를 기부를 위한 쓰기 프로그램의 일환으로 기부 대상으로 선택했습니다.
소개
견고한 테스트 커버리지를 확보하는 것은 웹 애플리케이션을 믿음직스럽게 구축하는 데 필수적입니다. Jest는 테스트를 작성하고 실행하는 데 도움이 되는 자바스크립트 테스트 러너입니다. React Testing Library는 구성 요소의 구현 세부 정보가 아닌 사용자 상호 작용을 기반으로 테스트를 구성하는 데 도움이 되는 일련의 테스트 도우미를 제공합니다. Jest와 React Testing Library는 Create React App에 미리 패키지로 제공되며 앱을 테스트하는 것이 소프트웨어가 사용될 것과 유사해야 한다는 지침을 준수합니다.
이 튜토리얼에서는 다양한 UI 요소가 포함된 샘플 프로젝트에서 비동기 코드와 상호 작용을 테스트할 것입니다. Jest를 사용하여 단위 테스트를 작성하고 실행하고 React Testing Library를 헬퍼 DOM(문서 객체 모델) 라이브러리로 구현하여 구성 요소와 상호 작용을 처리할 것입니다.
필수 조건
이 튜토리얼을 완료하기 위해 다음이 필요합니다:
-
로컬 컴퓨터에 설치된 Node.js 버전 14 이상. macOS 또는 Ubuntu 18.04에 Node.js를 설치하려면 macOS에 Node.js 설치 및 로컬 개발 환경 만들기 또는 PPA를 사용하여 설치 섹션의 단계를 따르십시오. Ubuntu 18.04에 Node.js 설치하는 방법.
-
로컬 머신에
npm
버전 5.2 이상이 있어야 합니다. 이를 통해 Create React App과 샘플 프로젝트에서npx
를 사용해야 합니다.Node.js
와 함께npm
을 설치하지 않았다면 지금 설치하세요. Linux의 경우sudo apt install npm
명령을 사용하십시오.- 이 튜토리얼에서
npm
패키지를 사용하려면build-essential
패키지를 설치해야 합니다. Linux의 경우sudo apt install build-essential
명령을 사용하십시오.
- 이 튜토리얼에서
-
로컬 머신에 Git이 설치되어 있어야 합니다. Git이 컴퓨터에 설치되어 있는지 확인하거나 운영 체제에 맞는 설치 프로세스를 진행할 수 있습니다. Ubuntu 20.04에 Git 설치 방법을 참조하세요.
-
React.js 코딩하는 방법 시리즈로 개발할 수 있는 React에 대한 친숙함이 필요합니다. 샘플 프로젝트가 Create React App으로 시작되었으므로 별도로 설치할 필요가 없습니다.
-
일부 Jest에 대한 익숙함은 테스트 러너나 프레임워크로 유용하지만 필수는 아닙니다. Jest는 Create React App과 함께 미리 패키지로 제공되므로 별도로 설치할 필요가 없습니다.
단계 1 — 프로젝트 설정
이 단계에서는 샘플 프로젝트를 복제하고 테스트 스위트를 시작할 것입니다. 샘플 프로젝트는 Create React App, Jest 및 React Testing Library라는 세 가지 주요 도구를 활용합니다. Create React App은 단일 페이지 React 애플리케이션을 부트스트랩하는 데 사용됩니다. Jest는 테스트 실행기로 사용되며 React Testing Library는 사용자 상호 작용을 중심으로 테스트를 구성하는 데 도움이 되는 테스트 도우미를 제공합니다.
시작하려면 GitHub에서 미리 빌드된 React App을 복제합니다. 도기 디렉터리 앱을 사용할 것입니다. 이 앱은 특정 견종을 기반으로 개 이미지 컬렉션을 검색하고 표시하는 시스템을 구축하는 데 도기 API를 활용하는 샘플 프로젝트입니다.
프로젝트를 Github에서 복제하려면 터미널을 열고 다음 명령을 실행하십시오:
다음과 유사한 출력이 표시됩니다:
OutputCloning into 'doggy-directory'...
remote: Enumerating objects: 64, done.
remote: Counting objects: 100% (64/64), done.
remote: Compressing objects: 100% (48/48), done.
remote: Total 64 (delta 21), reused 55 (delta 15), pack-reused 0
Unpacking objects: 100% (64/64), 228.16 KiB | 3.51 MiB/s, done.
doggy-directory
폴더로 이동하십시오:
프로젝트 종속성을 설치하십시오:
npm install
명령은 package.json
파일에 정의된 모든 프로젝트 종속성을 설치합니다.
종속성을 설치한 후에는 앱의 배포 버전을 확인하거나 다음 명령을 사용하여 로컬에서 앱을 실행할 수 있습니다:
로컬에서 앱을 실행하면 http://localhost:3000/
에서 열립니다. 터미널에서 다음 출력이 표시됩니다:
OutputCompiled successfully!
You can now view doggy-directory in the browser.
Local: http://localhost:3000
On Your Network: http://network_address:3000
실행 후 앱의 랜딩 페이지는 다음과 같이 보일 것입니다:
프로젝트 종속성이 설치되었으며 앱이 실행 중입니다. 다음으로 새 터미널을 열고 다음 명령을 사용하여 테스트를 시작하십시오:
npm test
명령은 Jest를 테스트 러너로 사용하여 대화형 감시 모드에서 테스트를 시작합니다. 감시 모드에서는 파일이 변경된 후 자동으로 테스트가 다시 실행됩니다. 파일을 변경할 때마다 테스트가 실행되며 해당 변경 사항이 테스트를 통과했는지 여부를 알려줍니다.
npm test
를 처음 실행한 후 터미널에서 다음 출력을 볼 수 있습니다:
OutputNo tests found related to files changed since last commit.
Press `a` to run all tests, or run Jest with `--watchAll`.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
이제 예제 애플리케이션 및 테스트 스위트가 실행되므로 랜딩 페이지를 테스트할 수 있습니다.
단계 2 — 랜딩 페이지 테스트
기본적으로 Jest는 .test.js
접미사가 있는 파일과 __tests__
폴더의 .js
접미사가 있는 파일을 찾습니다. 관련 테스트 파일을 수정하면 자동으로 감지됩니다. 테스트 케이스가 수정될 때 출력이 자동으로 업데이트됩니다. doggy-directory
샘플 프로젝트용으로 준비된 테스트 파일은 테스트 패러다임을 추가하기 전에 최소한의 코드로 설정되어 있습니다. 이 단계에서는 앱의 랜딩 페이지가 검색을 수행하기 전에 로드되는지 확인하기 위해 테스트를 작성합니다.
편집기에서 src/App.test.js
를 열어 다음 코드를 확인하십시오:
A minimum of one test block is required in each test file. Each test block accepts two required parameters: the first argument is a string representing the name of the test case; the second argument is a function that holds the expectations of the test.
함수 내부에는 React Testing Library가 컴포넌트를 DOM으로 렌더링하는 데 사용하는 render
메서드가 있습니다. 테스트 환경의 DOM에 렌더링된 테스트할 컴포넌트가 있으므로 이제 예상된 기능에 대한 코드를 작성하여 assert할 수 있습니다.
렌더링 메서드에 테스트 블록을 추가하여 API 호출이나 선택을 하기 전에 랜딩 페이지가 정확히 렌더링되는지 테스트합니다. render
메서드 아래에 강조된 코드를 추가하세요:
expect
함수는 특정 결과를 확인하려는 경우마다 사용되며, 코드가 생성하는 값을 나타내는 단일 인수를 허용합니다. 대부분의 expect
함수는 특정 값을 확인하기 위한 matcher 함수와 함께 사용됩니다. 이러한 단언의 대부분에 대해 DOM에서 발견된 일반적인 측면을 확인하기 쉽도록 제공되는 추가 매처를 사용할 수 있습니다. 예를 들어, 첫 번째 줄의 .toHaveTextContent
는 expect
함수의 매처이며, getByRole("heading")
는 DOM 요소를 가져 오는 선택기입니다.
React Testing Library는 테스트 DOM 환경에 대해 단언할 필요가 있는 필수 쿼리에 액세스하는 편리한 방법으로 screen
객체를 제공합니다. 기본적으로 React Testing Library는 DOM 내에서 요소를 찾을 수 있게 해주는 쿼리를 제공합니다. 쿼리의 주요 카테고리는 세 가지입니다:
getBy*
(가장 일반적으로 사용됨)queryBy*
(요소의 부재를 테스트하고 오류를 던지지 않을 때 사용됨)findBy*
(비동기 코드를 테스트할 때 사용됨)
각 쿼리 유형은 이후 자습서에서 정의될 특정 목적을 제공합니다. 이 단계에서는 가장 일반적인 쿼리 유형인 getBy*
쿼리에 집중하게 됩니다. 다양한 쿼리 변형에 대한 상세한 목록을 보려면 리액트의 쿼리 치트시트를 확인할 수 있습니다.
다음은 Doggy Directory 랜딩 페이지의 각 섹션을 나타내는 주석이 달린 이미지입니다. 첫 번째 테스트(랜딩 페이지 렌더링)가 다루는 내용입니다:
expect
함수는 다음에 대해 단언합니다(위의 주석이 표시된 내용):
- heading 역할을 가진 요소가 부분 문자열 일치인 Doggy Directory를 가져야 함을 기대합니다.
- 선택 입력의 정확한 표시 값은 Select a breed 여야 합니다.
- 검색 버튼은 선택이 이루어지지 않았기 때문에 비활성화되어 있어야 합니다.
- 검색이 이루어지지 않았으므로 플레이스홀더 이미지가 문서에 있어야 합니다.
완료되면 src/App.test.js
파일을 저장하십시오. 테스트가 감시 모드에서 실행되므로 변경 사항이 자동으로 등록됩니다. 변경 사항이 자동으로 등록되지 않는 경우 테스트 스위트를 중지하고 다시 시작해야 할 수 있습니다.
이제 터미널에서 테스트를 확인하면 다음 출력이 표시됩니다:
Output PASS src/App.test.js
✓ renders the landing page (172 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.595 s, estimated 5 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
이 단계에서는 Doggy Directory 랜딩 페이지의 초기 렌더링 보기를 확인하기 위한 초기 테스트를 작성했습니다. 다음 단계에서는 비동기 코드를 테스트하기 위해 API 호출을 모의하는 방법을 배우게 됩니다.
단계 3 – fetch
메서드 모의하기
이 단계에서는 JavaScript의 fetch
메서드를 모의하는 한 가지 접근 방법을 검토합니다. 이를 달성하는 다양한 방법이 있지만, 이 구현은 Jest의 spyOn
및 mockImplementation
메서드를 사용합니다.
외부 API에 의존하는 경우, 해당 API가 다운되거나 응답을 반환하는 데 시간이 걸릴 수 있습니다. fetch
메서드를 모의화하면 일관된 예측 가능한 환경을 제공하여 테스트에 대한 더 많은 신뢰를 제공합니다. 외부 API를 사용하는 테스트를 올바르게 실행하려면 API 모의 메커니즘이 필요합니다.
참고: 이 프로젝트를 단순화하기 위해, fetch 메서드를 모의(mock)화할 것입니다. 그러나 더 견고한 솔루션인 모의 서비스 워커 (MSW)와 같은 솔루션을 사용하는 것이 좋습니다. 이는 대규모의 제작용 코드베이스에서 비동기 코드를 모의(mock)하는 데 필요합니다.
src/mocks/mockFetch.js
파일을 열어 mockFetch
메서드가 어떻게 작동하는지 검토하세요:
mockFetch
메서드는 응용 프로그램 내에서 API 호출에 대한 응답으로 반환될 fetch 호출의 구조와 유사한 객체를 반환합니다. mockFetch
메서드는 Doggy Directory 앱의 두 가지 영역 간에 비동기 기능을 테스트하는 데 필요합니다: 종 목록을 채우는 선택 드롭다운과 검색을 수행할 때 개 이미지를 검색하기 위한 API 호출입니다.
src/mocks/mockFetch.js
파일을 닫으세요. 이제 테스트 파일에 mockFetch
메서드를 가져와서 사용 방법을 이해했으므로, 이를 테스트 파일에 가져올 수 있습니다. mockFetch
함수는 mockImplementation
메서드에 인수로 전달되며, 그런 다음 fetch API의 가짜 구현으로 사용됩니다.
src/App.test.js
에 mockFetch
메서드를 가져오기 위해 코드를 추가하세요:
이 코드는 각 테스트가 레벨한 기준에서 시작하도록 모의 구현을 설정하고 해체합니다.
jest.spyOn(window, "fetch");
는 DOM의 전역 변수에 연결된 fetch
메서드의 호출을 추적할 모의 함수를 생성합니다.
.mockImplementation(mockFetch);
함수를 허용하여 모의 메서드를 구현할 것입니다. 이 명령은 원래 fetch
구현을 무시하고, 앱 코드 내에서 fetch
가 호출될 때마다 실행됩니다.
작업이 완료되면 src/App.test.js
파일을 저장하십시오.
이제 터미널에서 테스트를 확인하면 다음과 같은 출력을 받게됩니다:
Output console.error
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
at App (/home/sammy/doggy-directory/src/App.js:5:31)
18 | })
19 | .then((json) => {
> 20 | setBreeds(Object.keys(json.message));
| ^
21 | });
22 | }, []);
23 |
...
PASS src/App.test.js
✓ renders the landing page (429 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.178 s, estimated 2 s
Ran all test suites related to changed files.
이 경고는 예상하지 않은 상태 업데이트가 발생했음을 알려줍니다. 그러나 출력은 또한 테스트가 fetch
메서드를 성공적으로 시뮬레이션했음을 나타냅니다.
이 단계에서 fetch
메서드를 모의하고 해당 메서드를 테스트 스위트에 통합했습니다. 테스트는 통과하지만 경고를 처리해야 합니다.
단계 4 — act
경고 수정
이 단계에서는 단계 3의 변경 후에 발생한 act
경고를 수정하는 방법을 배우게 됩니다.
act
경고는 fetch
메서드를 모의화했기 때문에 발생하며, 구성 요소가 마운트될 때 API 호출을 수행하여 견종 목록을 가져옵니다. 견종 목록은 선택 입력 내의 option
요소를 채우는 상태 변수에 저장됩니다.
아래 이미지는 견종 목록을 채우기 위해 성공적인 API 호출 후 선택 입력이 어떻게 보이는지 보여줍니다:
경고가 발생하는 이유는 상태가 테스트 블록이 구성 요소를 렌더링한 후에 설정되기 때문입니다.
이 문제를 해결하려면 src/App.test.js
의 테스트 케이스에 하이라이트된 수정을 추가하십시오:
async
키워드는 구성 요소가 마운트될 때 발생하는 API 호출의 결과로 비동기 코드가 실행됨을 Jest에 알려줍니다.
A new assertion with the findBy
query verifies that the document contains an option with the value of husky
. findBy
queries are used when you need to test asynchronous code that is dependent on something being in the DOM after a period of time. Because the findBy
query returns a promise that gets resolved when the requested element is found in the DOM, the await
keyword is used within the expect
method.
수정이 완료되면 src/App.test.js
에서 변경 사항을 저장하십시오.
새로운 추가 사항으로 인해 이제 테스트에서 act
경고가 더 이상 나타나지 않는 것을 확인할 수 있습니다:
Output PASS src/App.test.js
✓ renders the landing page (123 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.942 s, estimated 2 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
이 단계에서는 비동기 코드를 처리할 때 발생할 수 있는 act
경고를 수정하는 방법을 배웠습니다. 다음으로는 Doggy Directory 애플리케이션의 대화형 기능을 검증하기 위해 두 번째 테스트 케이스를 추가할 것입니다.
단계 5 — 검색 기능 테스트
마지막 단계에서는 검색 및 이미지 표시 기능을 확인하기 위한 새로운 테스트 케이스를 작성할 것입니다. 적절한 테스트 커버리지를 달성하기 위해 다양한 쿼리와 API 방법을 활용할 것입니다.
에디터에서 src/App.test.js
파일로 돌아가십시오. 파일 상단에는 다음과 같이 강조된 명령어를 사용하여 user-event
동반 라이브러리와 waitForElementToBeRemoved
비동기 메서드를 가져와 테스트 파일에 가져옵니다:
이러한 가져오기를 이 섹션에서 나중에 사용할 것입니다.
초기 test()
메서드 이후에 새로운 비동기 테스트 블록을 추가하고 다음 코드 블록으로 App
컴포넌트를 렌더링하십시오:
컴포넌트가 렌더링된 후에는 Doggy Directory 앱의 대화형 기능을 확인하는 함수를 추가할 수 있습니다.
src/App.test.js
에서 여전히 두 번째 test()
메서드 내에 강조된 코드 블록을 추가하십시오:
위에서 강조된 섹션은 개 종을 선택하고 올바른 값이 표시되는지 확인합니다.
getByRole
쿼리는 선택한 요소를 가져와 select
변수에 할당합니다.
Step 4에서 act
경고를 수정한 방식과 유사하게, 추가적인 어설션을 진행하기 전에 문서에 cattledog
옵션이 나타날 때까지 findByRole
쿼리를 사용하여 대기합니다.
이전에 가져온 userEvent
개체는 일반적인 사용자 상호 작용을 시뮬레이션합니다. 이 예에서 selectOptions
메서드는 이전 라인에서 대기한 cattledog
옵션을 선택합니다.
마지막 라인은 위에서 선택한 cattledog
값을 포함하는 select
변수를 확인합니다.
자바스크립트 test()
블록에 추가할 다음 섹션은 선택한 품종을 기반으로 강아지 이미지를 찾는 검색 요청을 시작하고 로딩 상태의 존재를 확인합니다.
다음과 같이 강조된 줄을 추가하십시오:
getByRole
쿼리는 검색 버튼을 찾아 searchBtn
변수에 할당합니다.
toBeDisabled
jest-dom matcher는 품종 선택이 이루어지면 검색 버튼이 비활성화되지 않았는지 확인합니다.
userEvent
개체의 click
메서드는 검색 버튼을 클릭하는 것을 시뮬레이션합니다.
waitForElementToBeRemoved
비동기 도우미 함수는 이전에 가져온 것으로, 검색 API 호출이 진행 중일 때 Loading 메시지의 출현과 사라짐을 기다립니다. waitForElementToBeRemoved
콜백 내의 queryByText
는 오류를 발생시키지 않고 요소의 부재를 확인합니다.
아래 이미지는 검색이 진행 중일 때 표시되는 로딩 상태를 보여줍니다:
다음으로, 이미지와 결과 수 표시를 유효성 검사하기 위해 다음 자바스크립트 코드를 추가하십시오:
getAllByRole
쿼리는 모든 개 이미지를 선택하고 그것들을 dogImages
변수에 할당합니다. 쿼리의 *AllBy*
변형은 지정된 역할과 일치하는 여러 요소가 포함된 배열을 반환합니다. *AllBy*
변형은 단일 요소만 반환할 수 있는 ByRole
변형과 다릅니다.
모의 fetch
구현은 응답 내에 두 개의 이미지 URL을 포함합니다. Jest의 toHaveLength
매처를 사용하여 두 개의 이미지가 표시되는지 확인할 수 있습니다.
getByText
쿼리는 올바른 결과 수가 오른쪽 상단에 나타나는지 확인합니다.
toHaveAccessibleName
매처를 사용하여 두 가지 어설션은 각 이미지와 적절한 대체 텍스트가 연관되어 있는지 확인합니다.
A completed search displaying images of the dog based on the breed selected along with the number of results found will look like this:
새로운 자바스크립트 코드 조각을 모두 결합하면 App.test.js
파일은 다음과 같이 보일 것입니다:
src/App.test.js
에서 변경 사항을 저장합니다.
테스트를 검토할 때, 터미널의 최종 출력은 이제 다음과 같이 됩니다:
Output PASS src/App.test.js
✓ renders the landing page (273 ms)
✓ should be able to search and display dog image results (123 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.916 s
Ran all test suites related to changed files.
Watch Usage: Press w to show more.
이 최종 단계에서 Doggy Directory 애플리케이션의 검색, 로딩 및 표시 기능을 검증하는 테스트를 추가했습니다. 최종 어설션이 작성되어 앱이 작동하는지 확인할 수 있습니다.
결론
이 튜토리얼에서는 Jest, React Testing Library 및 jest-dom 매처를 사용하여 테스트 케이스를 작성했습니다. 점진적으로 구축하면서 사용자가 UI와 상호 작용하는 방식에 따라 테스트를 작성했습니다. 또한 getBy*
, findBy*
, queryBy*
쿼리의 차이 및 비동기 코드를 테스트하는 방법에 대해 배웠습니다.
위에서 언급한 주제에 대해 더 알아보려면 Jest, React Testing Library, jest-dom 공식 문서를 참조하십시오. 또한 React Testing Library 작업 시 모범 사례에 대해 알아보려면 Kent C. Dodd의 React Testing Library의 일반적인 실수를 읽어보세요. React 앱 내에서 스냅샷 테스트를 사용하는 방법에 대해 자세히 알아보려면 스냅샷 테스트 작성 방법을 확인하십시오.