pile·
프론트엔드·더핑크퐁컴퍼니더핑크퐁컴퍼니·

react-testing-library 의 fireEvent 를 userEvent로 마이그레이션 하기 with Antd

더핑크퐁컴퍼니가 사내 백오피스의 테스트 코드를 `fireEvent` 에서 `userEvent` 로 옮기며 겪은 일을 정리한 글이다. BDT 와 IDT 의 차이에서 시작해, Antd 컴포넌트가 표준 HTML 요소를 쓰지 않아 생기는 문제와 그 mocking 과정의 삽질까지 담았다.

핵심 포인트
  • testing-library 는 BDT(행위 주도 테스트) 기반이다. 콘텐츠가 어떤 태그에 들어 있는지가 아니라 화면에 실제로 그려졌는지를 본다.
  • `fireEvent.click` 은 click 이벤트만 발생시키지만, `userEvent.click` 은 element 의 tagName 을 확인한 뒤 실제 브라우저에서 함께 일어나는 이벤트까지 발생시킨다.
  • 입력값 변경은 `change` 대신 `type` 을 쓴다. defaultValue 가 있으면 `fireEvent` 는 덮어쓰지만 `userEvent` 는 뒤에 덧붙이므로, 새 값만 넣으려면 `clear` 를 먼저 호출해야 한다.
  • 저자는 이 동작이 더 정확하다고 본다. 유저는 input 의 값을 지운 적이 없기 때문이다.
  • v14 부터 userEvent API 의 반환 타입이 Promise 로 바뀌어 비동기 테스트에서 await 를 걸 수 있게 됐다.
상세 정리
  • BDT 대 IDT: enzyme 같은 구현 주도 테스트는 "Hello My Test 가 div 안에 있는가" 를 보지만, testing-library 는 "Hello My Test 가 페이지에 그려졌는가" 를 본다. 유저에게는 태그 구조가 아니라 보이는지가 중요하기 때문이다.
  • v14 주요 변경: `pointer` 의 `skipPointerEvents` 가 사라지고 `pointerEventsCheck: PointerEventsCheckLevel.Never` 로 대체됐으며, `upload` 의 `applyAccept` 기본값이 true 가 되고, `paste` 에 새 매개변수가 생겼으며, `setup` API 가 추가되고 `tab()` 의 `focusTrap` 옵션이 제거됐다.
  • 문제 ① 클릭할 수 없는 요소: UI 라이브러리는 기능에 맞는 HTML 요소 대신 div·span 으로 모양과 기능을 흉내 내는 경우가 있다. Antd 의 select 는 `HTMLSelectElement` 가 아니라 div·span 이고, date picker 의 날짜 선택은 table 이다.
  • date picker 테스트 절차는 input 클릭 → 시작 날짜 클릭 → 종료 날짜 선택 → start/end input 갱신이다. `fireEvent` 로는 mouseDown·click·change 를 다 써야 했다.
  • `userEvent` 로 옮기면 mouseDown 같은 것을 신경 쓸 필요가 없어지지만, 선택한 태그가 `td` 라 "클릭할 수 있는 요소가 아니다" 는 에러가 난다.
  • 해결은 CSS 속성을 바꾸는 것이 아니라 `pointerEventsCheck` 옵션을 쓰는 것이라고 짚는다.
  • 문제 ② multiple select: `selectOptions` 는 select 태그에만 쓸 수 있어 Antd select 는 컴포넌트를 mocking 해야 한다. 이 API 는 누적 설계라 여러 번 호출하면 여러 개가 선택된다.
  • 그런데 a 를 고르고 b 를 골랐는데 a 만 두 번 선택되는 현상이 났다. 원인을 못 찾아 mocking 코드를 화면에 그려 놓고 값이 바뀔 때마다 console 을 찍어 추적했다.
  • 진짜 원인은 mocking 코드에 있었다. `value` 는 첫 번째로 선택한 옵션의 값만 주기 때문에, 다중 선택 상황에서 잘못된 값을 넘기고 있었다.
  • 여기서 얻은 교훈을 "UI 라이브러리 테스트를 위해 컴포넌트를 mocking 할 때는 HTML 의 기본 특성을 잘 알아야 한다" 로 정리한다.
  • 문제 ③ blur 테스트: 필수 입력 문구를 검증할 때 `fireEvent` 는 blur 를 직접 쏘면 되지만, `userEvent` 에서는 실제 구현대로 input 을 클릭한 뒤 아무 데나 다른 곳을 클릭하는 흐름으로 써야 한다.
  • 마무리에서 커버리지 100%는 굳이 필요할까 싶지만 비즈니스 로직을 담당하는 부분의 테스트는 수고를 덜고 생산성을 높인다는 개인 의견을 남긴다.
왜 읽나Antd 처럼 표준 요소를 쓰지 않는 UI 라이브러리에서 testing-library 테스트를 옮기려는 개발자에게, 어디서 막히고 무엇을 mocking 해야 하는지 실제 사례로 보여준다.
더핑크퐁컴퍼니
더핑크퐁컴퍼니 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2