UI 컴포넌트 테스트
UI 컴포넌트 테스트
웹 프론트엔드 주요 개발 대상은 UI 컴포넌트, 컴포넌트에는 렌더링 뿐만 아니라 복잡한 로직이 포함될 때가 많다.
컴포넌트를 테스트할 때 어떤 부분에 중점을 두는 것이 좋은지 살펴보자.
UI 컴포넌트의 최소 단위는 버튼과 같은 개별 UI
작은 UI 컴포넌트를 조합하여 중간 크기 컴포넌트를 만들며, 작은 단위부터 하나씩 조합해 화면 단위의 UI를 완성한다.
만약 고려해야할 사항을 빠뜨려서 중간 크기의 UI 컴포넌트에 문제가 생기면 페이지 전체에 문제가 생겨 앱을 사용하지 못하게 될 수도 있다.
UI 컴포넌트에 테스트를 작성해야 하는 이유
UI 컴포넌트 기능
"의도한 대로 작동하고 있는가", "문제가 생긴 부분은 없는가"를 확인해야 한다.
데이터를 렌더링하는 기능
사용자의 입력을 전달하는 기능
웹 API와 연동하는 기능
데이터를 동적으로 변경하는 기능
웹 접근성 테스트
신체적, 정신적 특성에 따른 차이 없이 정보에 접근할 수 있는 정도를 웹 접근성이라 한다.
웹 접근성은 홤녀에 보이는 문제가 아니라서 의식적으로 신경써야만 알 수 있다.
디자인에 문제 없고 정상적으로 동작한다면 품질에 문제가 없다고 생각하기 때문
웹 접근성을 신경쓰지 않으면 사용자 특성에 따라 접근조차 못하는 기능이 생기고 만다.
마우스를 쓰는 사용자와 보조 기기를 쓰는 사용자가 동일하게 요소들을 인식할 수 있는 쿼리로 테스트를 작성해야하기 때문에 UI 컴포넌트는 기본적인 기능의 테스트 뿐만 아니라 웹 접근성 품질 향상에도 도움이 된다.
테스트 환경 구축
UI 컴포넌트 테스트는 렌더링된 UI를 조작하고, 조작 때문에 발생한 결과를 검증하는 방식으로 진행된다.
라이브러리 설치
jest-environment-jsdom
@testing-library/react
@testing-library/jest-dom
@testing-library/user-event
jsdom
jsdom이 필요한 이유: UI를 렌더링하고 조작하려면 DOM API가 필요하지만 제스트가 테스트를 실행하는 환경인 Node.js에서는 공식적으로 DOM API를 지원하지 않기 때문
jest-envrionment-jsom: jsdom을 개선한 버전
Next.js 처럼 서버와 클라이언트 코드가 공존하는 경우는 테스트 파일 첫 줄에 다음과 같은 주석을 작성해 파일별로 다른 테스트 환 경을 사용하도록 설정할 수 있다.
테스팅 라이브러리
UI 컴포넌트를 테스트하는 라이브러리
테스팅 라이브러리의 역할
UI 컴포넌트를 렌더링한다
렌더링된 요소에서 임의의 자식 요소를 취득한다
렌더링된 요소에 인터렉션을 일으킨다.
"테스트는 소프트웨어의 사용법과 유사해야 한다." - 테스팅 라이브러리의 기본 원칙
클릭, 마우스오버, 키보드 입력 같은 기능을 사용하여 실제 웹 앱을 조작할 때와 유사하게 테스트를 작성할 것을 권장한다.
리엑트를 사용한다면
@testing-library/react
를 사용하면 된다.공통적으로
@testing-library/dom
을 코어로 사용하므로 뷰나 다른 컴포넌트 라이브러리를 사용하더라도 유사한 테스트 코드를 작성하게 된다.
DOM 상태를 검증할 때는 제스트 매처만으로 부족하기 때문에
@testing-library/jest-dom
을 사용하여 매처를 확장한다.커스텀 매처라는 제스트의 확장 기능을 제공, UI 컴포넌트를 쉽게 테스트할 수 있는 여러 매처를 사용 가능
사용자 입력 시뮬레이션 라이브러리
테스팅 라이브러리는 사용자 인터렉션 이벤트를 발생시키기 위해 fireEvent API를 제공한다
다만 fireEvent API는 DOM 이벤트를 발생시킬 뿐만 아니라 실제 사용자라면 불가능한 입력 패턴을 만들기도 한다
실제 사용자의 입력에 가깝게 시뮬레이션하기 위해서는
@testing-library/user-event
를 추가로 사용하는 것이 좋다.
UI 컴포넌트 렌더링
테스팅 라이브러리의
render
함수를 사용해서 렌더링
특정 DOM 요소 취득하기
렌더링된 요소 중 특정 DOM 요소를 취득하려면
screen.getByText
를 사용일치하는 문자열을 가진 한 개의 텍스트 요소를 찾는 API
요소를 발견하면 해당 요소의 참조를 취득
만약 요소를 찾지 못하면 오류 발생 후 테스트는 실패
단언문 작성
단언문은
@testing-library/jest-dom
으로 확장한 커스텀 매처를 사용toBeInTheDocument()
는 '해당 요소가 DOM에 마운트 됐는가?'를 검증하는 커스텀 매처
@testing-library/jest-dom
을 명시적으로 import
하지 않아도 사용 가능하다
jest-setup.ts
(모든 테스트에 적용할 설정 파일)에서 import 하고 있기 때문
특정 DOM 요소를 역할로 취득하기
screen.getByRole
: 특정 DOM 요소를 역할로 취득<button>
요소는 명시적으로 button 이라는 역할을 하진 않는다.그럼에도 button으로 취득할 수 있는 것은 암묵적 역할이라는 식별자를 테스팅 라이브러리가 지원하기 때문
테스팅 라이브러리는 암묵적 역할을 활용한 쿼리를 우선적으로 사용하도록 권장한다
역할은 웹 접근성 면에서 필수 개념
이벤트 핸들러 호출 테스트
이벤트 핸들러: 어떤 입력이 발생했을 때 호출되는 함수
이벤트 핸들러 호출은 목 함수로 검증한다
데이터가 주어질 때 조건부 렌더링 테스트
테스트는 아이템이 존재하는 경우와 존재하지 않는 경우의 분기 처리에 중점을 둬야 한다.
아이템이 존재하면 목록이 표시돼야 한다.
아이템이 존재하지 않으면 목록이 표시되지 않아야 한다.
테스트용 데이터(픽스처, fixture)는 목록을 표시하기 위한 배열 데이터
큰 컴포넌트를 다룰 때는 '테스트 대상이 아닌 listitem
'도 getByRole
의 반환값에 포함될 수 있다.
따라서 취득한 list 노드로 범위를 좁혀 여기에 포함된 listitem 요소의 숫자를 검증해야 한다.
대상 범위를 좁히고 시다면
within
함수르 사용within 함수의 반환값에는 screen과 동일한 요소 취득 API가 포함되어있음
목록에 표시할 내요이 없는 상황에서의 테스트
존재하지 않음을 테스트하려면
queryBy
접두사를 붙인 API를 사용해야 한다.get-
을 사용했을 때 해당 요소가 존재하지 않은 경우 오류를 발생시켜 테스트는 실패하게 됨
queryBy
는 취득할 요소가 없는 경우null
을 반환한다.not.toBeInTheDocument
또는toBeNull
로 검증컨벤션을 정해서 하나의 방식으로 통일하자.
개별 아이템 컴포넌트 테스트
예시로 개별 아이템이 props로 받은 id를 사용해서
더 알아보기
링크에 연결할 URL을 만드는 기능이 있다고치자
인터렉티브 UI 컴포넌트 테스트
체크박스를 클릭하면 onChange 이벤트 핸들러로 할당된 콜백함수가 실행됨
문자열을 입력하는 테스트
userEvent
를 사용한 모든 인터렉션은 력이 완료될 때까지 기다려야 하는 비동기 처리이므로 await을 사용해 입력이 완료될 떄까지 기다린다.
<input type="password" />
는 역할을 가지지 않는다?
패스워드 인풋은
textbox
역할이 아니다https://github.com/w3c/aria/issues/935
역할이 없는 경우에 요소를 취득하는데 대체 수단으로 placeholder 값을 참조하는 getByPlaceholderText를 사용해보자.
접근 가능한 이름
form에
aria-labelledby
를 사용하여 접근 가능한 이름으로 인용해보자.리엑트 18에 추가된 훅인
useId
를 사용하면 접근성에 필요한id
값을 자동으로 생성해준다.고유한 값을 관리하는 일은 번거로움 개이득
form
은 접근 가능한 이름을 할당하지 않으면 form이라는 역할을 가지지 않는다.
유틸리티 함수를 활용한 테스트
사용자의 입력이 검증의 기점이 되는데 입력 인터렉션을 함수화해서 활용해보자.
폼 입력은 여러 번 동일한 인터렉션을 작성해야 할 때가 많다.
이렇게 반복적으로 호출해야 하는 인터렉션을 하나의 함수로 정리하면 여러 곳에서 재사용할 수 있다.
이벤트 검증을 위한 목함수
비동기 처리가 포함된 UI 컴포넌트 테스트
handleSubmit
: form 에 전송된 값을 values라는 객체로 변환한다checkPhoneNumber
: 전송된 값에 유효성 검사를 실시postMyAddress
: 웹 API 클라이언트에 호출
입력된 값을 전송하는 인터렉션 함수
입력란에 모두 입력한 후 전송하는 과정을 비동기 함수로 정리
응답 성공 테스트
유효성 검사 테스트
유효성 검사를 사용하는 로직은
try-catch
를 사용해보자.. 오호
'준비', '실행', '검증' 3단계로 정리한 테스트 코드를 AAA 패턴(arrange act assert pattern)이라고 하며, 가독성이 좋다.
비동기 처리가 있다면 오류 분기가 복잡해지는 상황이 많기 때문에 테스트를 작성하면서 누락된 부분이 없는지 확인해야 한다.
UI 컴포넌트 스냅 테스트
UI 컴포넌트가 예기치 않게 변경됐는지 검증해보고 싶다면 스냅샷 테스트를 해보자.
스냅샷 기록하기
UI 컴포넌트의 스냅샷 테스트를 실행하면 HTML 문자열로 해당 시점의 렌더링 결과를 외부 파일에 저장한다
toMatchSnapshot
단언문 실행테스트가 실행되면 테스트파일과 같은 경로에
__snapshots__
디렉토리가 생성됨디렉터리 내부에
테스트파일명.snap
형식으로 파일이 저장됨자동으로 생성되는
.snap
파일은 깃의 추적 대상으로 두고 커밋하는 것이 일반적 (.gitignore 추가 X)
회귀 테스트 발생시키기
스냅샷 테스트는 이미 커밋된
.snap
파일과 현시점의 스냅샷 파일을 비교하여 차이점이 발견되면 테스트를 실패하게 만든다테스트를 실행하면 변경된 부분에
diff
가 생기고 실패한다.UI 컴포넌트가 복잡해지면 의도하지 않은 변경 사항을 발견하는 경우도 있다.
스냅샷 갱신하기
실패한 테스트를 성공시키려면 커밋된 스냅샷을 갱신해야 한다.
테스트를 실행할 때
--updateSnapshot
혹은-u
옵션을 추가하면 스냅샷이 새로운 내용으로 갱신됨발견한 변경사항이 의도한 것이라면 갱신을 허가한다는 의미에서 새로 출력된 스냅샷을 커밋하자.
암묵적 역할과 접근 가능한 이름
getByRole
은 웹 기술 표준을 정하는 W3C의 WAI-ARIA라는 사양에 포함된 내용 중 하나WAI-ARIA에는 마크업만으로 부족한 정보를 보강하거나 의도한 대로 의미를 전달하기 위한 내용들이 있다.
WAI-ARIA 기반한 테스트 코드를 작성하면 스크린 리더 등의 보조 기기를 활용하는 사용자에게도 의도한 대로 컨텐츠가 도달했는지 검증할 수 있다.
보조기기 뿐만 아니라 테스팅 라이브러리에서도 동일한 암묵적 역할을 사용한다.
테스팅 라이브러리는 내부적으로
aria-query
라이브러리로 암묵적 역할을 취득함jsdom
은 접근성에 관여하지 않음
역할과 요소는 일대일로 매칭되지 않는다.
요소가 가진 암묵적 역할과 요소가 일대일로 매칭되지는 않는다.
암묵적 역할은 요소에 할당한 속성에 따라 변경됨
대표적인 경우가
input
type에 속성에 따라 암묵적 역할이 변함
또한
type
속성에 지정한 값과 역할 명칭이 반드시 일치하지도 않음
aria 속성을 활용해 추출
h1 ~ h6
은getByRole("heading", { level: x })
라는 쿼리로 특정
접근 가능한 이름을 활용하여 추출
접근 가능한 이름: 보조기기가 인식하는 노드의 명칭
예를 들면 버튼에 '전송'이라는 문자가 있으면 해당 요소는 '전송' 버튼으로 읽힌다.
아이콘만 있는 경우 어떤 기능을 제공하는 버튼인지 사용자에게 전달하진 못한다 (aria-label 필수!!)
logRoles
함수를 실행하면 접근 가능한 이름을 확인할 수 있다.취득 가능한 요소가
----
로 구분되어 로그가 출력됨접근성을 강화하거나 테스트 코드에 반영하는데 활용 가능
암묵적 역할 목록
<article>
article
<aside>
complementary
<nav>
navigation
<header>
banner
<footer>
contentinfo
<main>
main
<section>
region
aria-labelledby가 지정되야함
<form>
form
접근 가능한 이름을 가진 경우로 한정
<button>
button
<a href="xxxx">
link
href
속성을 가진 경우로 한정
<input type="xxx">
checkbox, radio, button, textbox, searchbox, spinbutton, slider
type="password"
는 역할 X
<select>
listbox
<optgroup>
group
<option>
option
<ul>
, <ol>
, <li>
list, listitem
<table>
table
<caption>
caption
<th>
, <td>
, <tr>
columnheader/rowheader, cell, row
<fieldset>
group
<legend>
X
쿼리(요소 취득 API)의 우선순위
테스팅 라이브러리는 '사용자 입력을 제약 없이 재현한다.'는 원칙이 있다.
요소 취득 API는 다음과 같은 순서로 사용할 것을 권장한다.
1. 모두가 접근 가능한 쿼리
웹 접근성에 준수하여 차이 없이 접근할 수 있는 쿼리를 의미
시각적으로 인지한 것과 스크린 리더등의 보조 기기로 인지한 것이 동일하다는 것을 증명 가능
getByRole
명시적으로
role
속성이 할당된 요소뿐만 아니라 암묵적 역할을 가진 요소도 취득 가능
getByLabelText
getByPlaceholerText
getByText
getByDisplayValue
2. 시맨틱 쿼리
공식 표준에 기반한 속성을 사용하는 쿼리를 의미
시멘틱 쿼리는 브라우저나 보조기기에 따라 상당히 다른 결과가 나올수 있는 것에 주의
getByAllText
getByTitle
3. 테스트 ID
테스트용으로 할당된 식별자를 의미
역할이나 문자 컨텐츠를 활용한 쿼리를 사용할 수 없거나 의도적으로 의미 부여를 피하고 싶을 때만 사용할 것을 권장
getByTestId
Last updated