testing library의 userEvent와 fireEvent는 무슨 차이점이 있을까?

이번에 testing library의 userEvent와 fireEvent의 차이점에 대해서 알아보았습니다.
2023.10.15

이번에 testing library의 userEvent와 fireEvent의 차이점에 대해서 알아보았습니다.

두 가지 모두 사용자와 브라우저 간의 이벤트의 상호 작용을 테스트하기 위해 이용합니다.

차이점

userEvent는 fireEvent보다 더 실제 브라우저와의 상호 작용을 유사하게 이벤트의 상호 작용을 테스트할 수 있습니다.

구체적으로는 Differences from fireEvent에서는 아래와 같은 케이스를 설명하고 있습니다.

It factors in that the browser e.g. wouldn't let a user click a hidden element or type in a disabled text box.

차이점 확인

위의 설명대로 실제로 코드를 작성 후 확인해 보았습니다.

  • 수행한 행동: 비활성화된 입력란에 각각 fireEvent와 userEvent를 이용해 입력 이벤트를 실행시킨다.
  • 원하는 결과: 비활성화된 필드이므로 결과는 빈 문자가 표시되어야 한다.
...
test('userEvent', async () => {
  const user = userEvent.setup()
  render(<input type="text" data-testid="name" disabled={true} />)

  const input = screen.getByTestId('name')
  expect(input).toBeDisabled()
  await user.type(input, '입력후')
  expect(screen.getByTestId('name')).toHaveValue('')
})
// result: success
test('fireEvent', async () => {
  render(<input type="text" data-testid="name" disabled={true} />)

  const input = screen.getByTestId('name')
  expect(input).toBeDisabled()
  fireEvent.change(input, { target: { value: '입력후' } })
  expect(screen.getByTestId('name')).toHaveValue('입력후')
})
// result: success

와 같이 실제 작성해 보니 fireEvent의 경우에는 원하는 결과가 되지 않는 걸 확인할 수 있었습니다.

보충

조금 아쉬운 느낌이 있었기 때문에 아래의 내용을 보고 조금 더 테스트 코드를 작성해 보았습니다.

Testing Library's built-in fireEvent is a lightweight wrapper around the browser's low-level dispatchEvent API, which allows developers to trigger any event on any element. The problem is that the browser usually does more than just trigger one event for one interaction. For example, when a user types into a text box, the element has to be focused, and then keyboard and input events are fired and the selection and value on the element are manipulated as they type.

의 설명을 보고 과연 userEvent의 경우 입력 과정 중의 focus 이벤트가 실행되는지 확인해 보았습니다.

  • 수행한 행동: 활성화된 입력란에 각각 fireEvent와 userEvent를 이용해 입력 이벤트를 실행시킨다.
  • 원하는 결과: 입력 과정 중 focus 이벤트가 실행되는지 확인한다.
...
test('userEvent', async () => {
  const user = userEvent.setup()
  const onFocus = vi.fn()
  render(
    <input
      type="text"
      data-testid="name"
      disabled={false}
      onFocus={onFocus}
    />
  )

  const input = screen.getByTestId('name')
  expect(input).not.toBeDisabled()
  await user.type(input, '입력')
  expect(screen.getByTestId('name')).toHaveValue('입력')
  expect(onFocus).toHaveBeenCalled()
})
// result: success
test('fireEvent', async () => {
    const onFocus = vi.fn()
    render(
      <input
        type="text"
        data-testid="name"
        disabled={false}
        onFocus={onFocus}
      />
    )

    const input = screen.getByTestId('name')
    expect(input).not.toBeDisabled()
    fireEvent.change(input, { target: { value: '입력' } })
    expect(screen.getByTestId('name')).toHaveValue('입력')
    expect(onFocus).not.toHaveBeenCalled()
  })
})
// result: success

와 같이 실제 작성해 보니 fireEvent와 userEvent가 서로 반대의 결과인 것을 확인 할 수 있었습니다.

참고자료