Frontend/React

프론트엔드 테스트 - (4) RTL - 쿼리 우선 순위, userEvent

rachel_13 2024. 3. 25. 00:39

이번에는 이전 포스팅에서 다뤘던 쿼리 함수들의 우선 순위userEvent 에 대해서 더 자세히 알아본다.

 

쿼리 함수 우선 순위

 

먼저, 쿼리 함수란 간단하게 요약하자면, 우리가 테스트를 할 때 요소를 직접적으로 가져오기 위한 수단 정도로 이해하면 쉽다.

타입은 세 가지 종류가 있었는데, [get, query, find]  세가지 종류가 있다. 

세 가지 종류의 타입에 대해서는 이전 포스팅에서 자세히 확인해 볼 수 있다.

 

이 쿼리 함수에도 공식 문서에서 권고하는 우선 순위가 있는데, 이에 대해서 다뤄보려고 한다.

 

일반적으로 테스트 코드는 사용자의 인터렉션 방식과 최대한 유사해야 한다.

 

1. Queries Accessible to Everyone.

:모든 사용자가 쿼리에 접근할 수 있어야 한다.

 

일반적으로 웹 사이트를 사용자에게 제공할 때, 장애인, 고령자들을 고려한 웹 접근성을 보장하는 것이 법적 의무사항이다.

앞서 일반적인 테스트 코드는 사용자의 인터렉션 방식과 최대한 유사해야 한다고 했다.

 

이처럼 누구나 쿼리에 접근할 수 있음을 보장 받을 수 있다는 전제하에 테스트 코드를 작성해야 하기 때문에, 쿼리 함수 역시

getByRole을 사용하라고 권고한다.

 

  • getByRole
    • 접근 가능한 트리에 대해 모든 요소를 사용할 수 있다.
    • name 옵션 사용시 요소들을 필터링 할 수 있다.
    • 이 쿼리를 사용할 수 없다는 것은 그만큼 UI가 접근성이 떨어진다는 것을 의미한다.

 

  • getByText
    • 양식(form)이 아닐 경우, 가장 흔하게 볼 수 있다.
    • 이 방법은 non-interactive한 요소(예: div, span, p)를 찾는 데 사용할 수 있다.
  • getByDisplayValue
    • 양식(form)에서 값이 채워져 있을 때 찾기 쉽다.

 

2. Semantic Queries

:시멘틱 쿼리 - ARIA compliant selectors

이러한 속성들과 상호작용하며 사용자 경험을 향상시키는 어플리케이션의 경우 아래와 같은 함수들을 사용할 수 있다.

  • getByAltText
    • img, area, input 태그에서 사용할 수 있는 alt 속성에 관한 것이다.
  • getByDisplayValue
    • 양식(form)에서 값이 채워져 있을 때 찾기 쉽다.

 

3. Test Ids

:테스트 Id(고유 식별자)

 

  • getByTestId
    • 사용자는 이 속성을 알 방법이 없다.
    • role이나 text를 일치시킬 수 없거나 의미가 없는 경우(예: 텍스트가 동적인 경우)에만 이 방법을 사용하는 것이 좋다.

 

 

 

UserEvent

 

useEvent는 사용자가 실제로 실행하는 인터렉션에 따른 테스트 코드를 구현하는데 적합한 메소드이다.

일반적으로 한 이벤트 호출에서 끝나지 않고 여러 이벤트들이 연쇄적으로 발생할 때를 고려해서 테스트 코드를 작성할 수 있기 때문에,

fireEvent 보다는 userEvent 사용을 권고 한다.

 

그렇다면, 앞선 예제에서 fireEvent로 구현했던 테스트 코드를 userEvent로 바꿔보자

 

1. 설치하기

npm install --save-dev @testing-library/user-event

 

 

2. 설정하기

 

사용자가 키보드를 누른다던지.. 등의 어떠한 인터렉션이 발생하면, 브라우저는 해당하는 UI 레이어를 보여준다.

그러면 브라우저는 이 입력을 해석하고 그에 따라 기본 DOM을 변경하고 신뢰할 수 있는 이벤트를 전송할 수 있다.

 

user-event는 이 UI 레이어를 mocking(모의)하여 실제 브라우저에서 발생하는 것처럼 사용자의 인터렉션을 시뮬레이션 하는 것이다.

 

setup(options?: Options): UserEvent

 

 

예시:

 

import userEvent from '@testing-library/user-event'

const user = userEvent.setup()

await user.keyboard('[ShiftLeft>]') // Press Shift (without releasing it)
await user.click(element) // Perform a click with `shiftKey: true`

 

 

3. 예제 코드 바꾸기

 

- user-event 14버전으로 버전업해서 인지, userEvent 스펙을 보니 따로 setup()메소드가 정의되어 있지 않았다.

- 그래서 별도로 세팅을 해주진 않음.

 

[참고] userEvent 인터페이스

declare const userEvent: {
    click: typeof click;
    dblClick: typeof dblClick;
    type: typeof type;
    clear: typeof clear;
    tab: typeof tab;
    hover: typeof hover;
    unhover: typeof unhover;
    upload: typeof upload;
    selectOptions: (args_0: Element, args_1: string | string[] | HTMLElement | HTMLElement[], args_2?: MouseEventInit | undefined, args_3?: import("./utils").PointerOptions | undefined) => void;
    deselectOptions: (args_0: Element, args_1: string | string[] | HTMLElement | HTMLElement[], args_2?: MouseEventInit | undefined, args_3?: import("./utils").PointerOptions | undefined) => void;
    paste: typeof paste;
    keyboard: typeof keyboard;
};

 

 

그리고 테스트 코드를 작성했더니...

 

 

 

 

마주한 에러 #1

:   Warning: An update to Counter inside a test was not wrapped in act(...).
    When testing, code that causes React state updates should be wrapped into act(...):

 

정확히는 경고이긴 한데, 무튼 경고가 발생한 이유는 React에서 state값이 업데이트 되면서 컴포넌트가 재 렌더링 되는데

이 렌더링이 끝나기 전에 외부에서 테스트 코드가 실행되기 때문에 발생하는 비동기 이슈이다.

(참고: https://davidwcai.medium.com/react-testing-library-and-the-not-wrapped-in-act-errors-491a5629193b)

따라서 act() 로 감싸주어야 에러가 나지 않는다.

 

그래서 act()로 감쌌더니..


 

마주한 에러 #2

이제 eslint에서 오류를 발생시킨다.

: Avoid wrapping Testing Library util calls in 'act'
요약하자면, lint 오류를 해결하기 위해 act를 쓰지 말라고 하는데....


"모든 테스트 라이브러리 유틸리티는 이미 act로 래핑되어 있습니다.

대부분의 경우, act 경고가 표시되면 단순히 넘어가야 하는 것이 아니라

실제로 테스트에서 예기치 않은 일이 발생하고 있음을 알려주는 것입니다. (일부 번역)"


https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-unnecessary-act.md

 

 

다시 정리해보자면,

 

비동기 로직이 포함된 컴포넌트를 테스트할 때 act()가 필요한 경우가 있다고 한다.

예를 들어, 컴포넌트가 마운트된 후 데이터를 가져오는 경우, 이 비동기 로직이 완료될 때까지 기다려야 한다.

이런 경우에는 act()를 비동기적으로 사용하거나 Testing Library의 waitFor, findBy와 같은 비동기 유틸리티를 사용해야 한다.

 


 

자, 그래서 waitFor 비동기 유틸리티를 사용해보기로 한다.

 

 

[예제 코드]

 

// + 버튼을 클릭하면 카운터가 1 증가한다.
test("+ 버튼을 클릭하면 카운터가 1 증가한다.", async () => {
  render(<Counter />);
  const plusButton = screen.getByRole("button", { name: /plus/i });
  await waitFor(async () => {
    await userEvent.click(plusButton);
  });

  const counterElement = screen.getByTestId("counter");
  expect(counterElement).toHaveTextContent("1");
});

// - 버튼을 클릭하면 카운터가 1 감소한다.
test("- 버튼을 클릭하면 카운터가 1 감소한다.", async () => {
  render(<Counter />);
  const minusButton = screen.getByRole("button", { name: /minus/i });
  await waitFor(async () => {
    await userEvent.click(minusButton);
  });

  const counterElement = screen.getByTestId("counter");
  expect(counterElement).toHaveTextContent("0");
});

// 카운터가 0일 때 - 버튼을 클릭하면 카운터는 0이다.
test("카운터가 0일 때 - 버튼을 클릭하면 카운터는 0이다.", async () => {
  render(<Counter />);
  const counterElement = screen.getByTestId("counter");
  expect(counterElement).toHaveTextContent("0");

  const minusButton = screen.getByRole("button", { name: /minus/i });
  await waitFor(async () => {
    await userEvent.click(minusButton);
  });

  expect(counterElement).toHaveTextContent("0");
});

// on/off 버튼을 클릭하면 +, - 버튼이 비활성화된다.
test("on/off 버튼을 클릭하면 +, - 버튼이 비활성화된다.", async () => {
  render(<Counter />);
  const onOffButton = screen.getByRole("button", { name: /onoff/i });
  await waitFor(async () => await userEvent.click(onOffButton));

  const minusButton = screen.getByRole("button", { name: /minus/i });
  const plusButton = screen.getByRole("button", { name: /plus/i });

  expect(minusButton).toBeDisabled();
  expect(plusButton).toBeDisabled();
});

 

 

 

[테스트 결과]

- 잘된다..! 😊

 

 

 

 

* 참고

따라하며 배우는 리액트 테스트

https://testing-library.com/docs/queries/about#priority

 

 

 

 

 

 

 

 

 

 

 

 

 

프론트엔드 테스트 - (3) React Testing Library 실습 (feat. TDD)

간단한 예제를 통해, React Testing Library를 실습해보고자 한다. 먼저 실습에 앞서, TDD 개념에 대해 짚고 가보자. TDD란 Test Driven Development의 약자로, 한국어로는 테스트 주도 개발이라고 명칭한다. 테

rachelslab.tistory.com