[React] RTL(React Testing Library)쿼리 우선 순위 + Jest 사용법 & TDD란?
Jest
- FaceBook에 의해서 만들어진 테스팅 프레임 워크이다.
- 최소한의 설정으로 동작하며 Test Case를 만들어서 애플리케이션 코드가 잘 돌아가는지 확인해 준다.
- 단위 (Unit) 테스트를 위해서 이용한다.
Jest가 테스트할 파일을 찾는 방법?
- 어떠한 파일에 테스트 코드를 작성해놓으면 Jest가 그 테스트 파일을 찾아서 테스트 케이스를 실행해준다.
테스트할 파일을 찾는 방법은?
1. 파일 이름 뒤에 test를 명시해 주거나
2. spec를 달아주거나
3. 폴더를 만드는데 그 폴더 이름을 "tests"라고 해주면
👉🏻 Jest가 알아서 이게 테스트를 위한 파일이구나~ 또는 폴더구나라고 인식하고 찾아서 그 안에 있는 테스트 케이스들을 실행함.
Jest 파일 구조
- jest에는 describe라는 게 있고, 그 안에 테스트 케이스들이 있다.
- 테스트 파일에 많은 수의 테스트 함수가 작성되어 있는 경우, 연관된 테스트 함수들끼리 그룹화해 놓으면 가독성이 좋아진다.
- "describe" (argument(name, fn)) : 여러 관련 테스트를 그룹화하는 블록을 만든다.
- "it" same as "test". (argument(name, fn, timeout)) : 개별 테스트를 수행하는 곳. 각 테스트를 작은 문장처럼 설명한다.
👉🏻 이 테스트 케이스들이 다 비슷한 것들이기 때문에 describe 안에 넣어주면 된다.
- "expect" : expect 함수는 값을 테스트할 때마다 사용된다. 그리고 expect 함수 혼자서는 거의 사용되지 않으며 matcher와 함께 사용된다.
- "matcher" : 다른 방법으로 값을 테스트하도록 "매처"를 사용한다.
matcher란 '이거 맞아?'라고 물어보는 메서드라고 보면 된다. 기대한 값이 실제 반환된 값과 일치하는 지를 확인하는 작업을 일컫는다.
(매처 종류는 다음 포스팅에서 다룰 예정)
test('two plus two is four', ()=>{
expect(2 + 2).toBe(4);
})
- 위 test를 보면 하나의 테스트 안에 테스트 설명을 적어둠
- expect는 2 + 2가 4라는 것을 기대함
- 거기에 matcher(=toBe(4)) 안에 실제의 값을 4로 넣어줌
👉🏻 이렇게 하면 실제로 테스트가 pass 하는 것을 볼 수 있다.
test('two plus two is four', ()=>{
expect(2 + 2).not.toBe(5);
})
- 이런 식으로. not()을 붙여서 실제 값이 아니라는 것을 명시해줄 수 있다.
👉🏻 이렇게 하면 테스트는 pass가 뜰 것.
React Testing Library
- "render" 함수 : DOM에 컴포넌트를 랜더링 하는 함수. 인자(괄호)로 랜더링 할 React 컴포넌트가 들어간다.
Return은 RTL에서 제공하는 쿼리 함수와 기타 유틸리티 함수를 담고 있는 객체를 리턴(Destructuring 문법으로 원하는 쿼리 함수만 얻어올 수 있다.)
👉🏻 소스 코드가 복잡해지면 비추천!!!! screen 객체를 사용하기. 이유는 사용해야 할 쿼리가 많아질수록 코드가 복잡해질 수 있음.
쿼리 함수란?
- 쿼리는 페이지에서 요소를 찾기 위해 테스트 라이브러리가 제공하는 방법이다.
여러 유형의 쿼리("get", "find", "query")가 있다.
이들 간의 차이점은 요소가 발견되지 않으면 쿼리에서 오류가 발생하는지 또는 Promise를 반환하고 다시 시도하는지 여부이다.
선택하는 페이지 콘텐츠에 따라 다른 쿼리가 다소 적절할 수 있다.
- 여러 가지 쿼리 함수가 있지만, 너무 많은 함수가 있어서 하나씩 불러오는 데는 불편함과 가독성이 안 좋음.
ex)
{getByText, getByLabelLabel, queryBy} = render(<.../>) 👉🏻 이런 식으로 하는것 보단
screen.getByText(...) 👉🏻 이런식으로 screen이라는 객체를 이용해서 query함수를 사용하면 된다.
get, find, query의 차이점
- "getBy..." : 쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없거나 둘 이상의 일치가 발견되면 설명 오류를 발생시킨다.
(둘 이상의 요소가 예상되는 경우 대신 getAllBy 사용.)
- "queryBy..." : 쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없으면 null을 반환한다.
이것은 존재하지 않는 요소를 어설션하는 데 유용하다. 둘 이상의 일치 항목이 발견되면 오류가 발생한다.
(확인된 경우 대신 queryAllBy 사용.)
- "findBy..." : 주어진 쿼리와 일치하는 요소가 발견되면 해결되는 Promise를 반환한다.
요소가 발견되지 않거나 기본 제한 시간인 1000ms 후에 둘 이상의 요소가 발견되면 약속이 거부된다.
(둘 이상의 요소를 찾아야 하는 경우 findAllBy를 사용.)
👉🏻 위의 세 가지는 하나 이상일 때는 사용 불가. 그럴 땐 "All"을 붙여서 사용하면 됨.
React Testing Library 주요 API
FireEvent API
- 유저가 발생시키는 액션(이벤트)에 대한 테스트를 해야 하는 경우 사용한다.
screen.getByTestId()
- getByTestId 쿼리를 이용해서 element에 접근해서 테스트를 진행하는 방법이 편리하긴 하지만 testing library에서 추천하는 쿼리 사용 우선순위가 있기 때문에 확인을 해보자.
🖐🏻쿼리 사용 우선순위
공식 문서에 따르면, 테스트는
사용자가 코드(구성 요소, 페이지 등)와 상호 작용하는 방식과 최대한 유사해야 한다고 한다.
간단하게 말하자면, 사용자는 [등록 버튼]을 누르는 것이지
[id 가 submit-button-test인 요소]를 누르는 게 아닌 것이다.
이러한 이유로 공식 문서에는 getByTestId보다 getByRole을 권장한다고 나와있다.
Role
- 모든 사람에게 접근 가능한 쿼리를 사용해야 하는데, 그럴 때 사용하는 (시각적이나, 보조기구를 사용하는 사람들을 반영한) 게
"role"이다. (= 즉 UX가 좋아짐)
👇🏻 아래는 롤에 대한 공식 문서이다.
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques#roles
ex)
getByRole('button', {name: /submit/i})
1. getByRole을 선언해 준다.
2. 괄호 안에 role 문법을 넣어준다. (button, redio... 등등)
3. name은 role의 이름이 submit이라고 되어 있으면 저렇게 넣어준다.(이름에 따라 다르게 넣어주면 됨)
4. "i" 는 대소문자 상관없이 확인해 주는 기능임.
❗️하지만 단점도 있다.
- 성능이 안 좋아서 테스트 속도가 느리다고 한다.
🖐🏻 그래도 getByRole을 사용해야 하는 이유는 정확한 테스트를 할 수 있기 때문이다.
예를 들어
👉🏻 getByTestId는 오타를 내더라도 통과가 될 수 있다.
(ex. 등록 -> '등럭' / data-testid는 변경 없으므로 통과)
🖐🏻 getByRole로 role과 name을 모두 써준다면
보다 정확하게 테스트를 할 수 있을 것이다.
... 이외에도 getByLabel, getByText 등등 여러 가지가 있다.
테스트 ID
getByTestId
- id는 개발자만 볼 수 있다.
- 이걸 사용할 때는 role도 사용할 수 없고, 텍스트, 어떠한 경우에도 사용할 수 없을 때 레코멘디드 되는 게 getByTestId다.
- 테스트 용으로 지정된 고유한 data-testid 속성을 가진 요소를 선택한다.
"data-testid" 속성은 테스트를 위한 목적으로 만들어진 고유 식별자로,
일반적으로 이 속성은 테스트의 목적으로만 사용되며,
사용자 인터페이스나 접근성에는 영향을 주지 않는다.
시멘틱 Query
getByAltText
- 이건 주로 이미지(img)나 Area, input 같은데에서 주로 사용한다.
- 대체 텍스트(alt text)에 따라 이미지나 다른 요소를 찾는다.
- 이미지의 alt 속성은 그 이미지가 표현하는 내용을 설명하는 텍스트이다.
- 이 속성은 보조 기술(예: 스크린 리더)에게 이미지의 내용을 전달하는 데 중요하며,
- 이미지가 로드되지 않을 때 표시된다.
🖐🏻 첫 테스팅에서는 버튼을 클릭했을 때 fireEvent API를 사용했다.
하지만 fireEvent 보다는 userEvent API를 사용하는 게 더 좋은 방법이다.
👉🏻 fireEvent.click() < userEvent.click()
ex)
예를 들어 fireEvent로 버튼을 클릭하면 fireEvent.click(button) 버튼이 focus 되지 않는다. ❌
하지만 userEvent로 클릭하면 userEvent.click(button) 버튼이 focus가 된다. ⭕️
👍🏻 이렇게 실제 사용하는 유저가 보기에 실제 버튼을 클릭하는 행위가 더 잘 표현되기에 userEvent를 사용하는게 더 추천되는 방법이다.
fireEvent
- React Testing Library에서 제공하는 유틸리티 함수 중 하나로 테스트 중에 이벤트를 발생시키는 데 사용한다.
React 컴포넌트를 테스트할 때 사용자 동작을 모의하고 특정 이벤트를 발생시켜 컴포넌트의 상태 변화 및 렌더링을 확인하는 데 유용하다.
click(), mouseover(), focus(), change()등 이벤트를 트리거할 수 있는 다양한 메소드들을 제공하고,
테스트 코드에서 적절한 엘리먼트에 이벤트를 fire 하면 이벤트가 발생하고
실제 컴포넌트에서 작성된 이벤트에 대한 변화를 확인할 수 있다.
userEvent
- userEvent는 fireEvent를 사용해서 만들어졌다.
userEvent의 내부 코드를 보면 fireEvent를 사용하면서 엘리먼트의 타입에 따라서 Label을 클릭했을 때,
checkbox, radio를 클릭했을 때 그 엘리먼트 타입에 맞는 더욱 적절한 반응을 보여준다.
실제 사용자가 수행하는 것과 보다 유사하게 이벤트를 발생시킨다고 한다.
테스트 주도 개발 TDD (Test Driven Development)
TDD란 무엇인가?
- 실제 코드를 작성하기 전에 테스트 코드를 먼저 작성한다.
테스트 코드를 작성한 후 그 테스트 코드를 Pass 할 수 있는 실제 코드를 작성한다.
🖐🏻 원하고자 하는 기능의 테스트 코드 작성 ➡️ 테스트 실행 Fail ➡️ 테스트 코드에 맞는 실제 코드 작성 ➡️ 테스트 실행 Pass
TDD를 하면 좋은 점?
1. TDD를 하므로 인해 많은 기능을 테스트하기에 소스 코드에 안정감이 부여된다.
2. 실제 개발하면서 많은 시간이 소요되는 부분은 디버깅 부분이기에 TDD를 사용하면 디버깅 시간이 줄어들고 실제 개발 시간도 줄어든다.
3. 소스 코드 하나하나를 더욱 신중하게 짤 수 있기 때문에 깨끗한 코드가 나올 확률이 높다.
참고
https://velog.io/@pds0309/react-testing-library-fireEvent-vs-userEvent
https://zzocco94.tistory.com/18