🧪

user-eventのclick()はホバーも(フォーカスも)再現する

2024/08/21に公開

はじめに

testing-library/user-eventを用いた以下のテストを実行すると結果はどうなるでしょうか?
user.click()なのでvalueの値は1になるでしょうか?

import React, { useState } from 'react'

import { render, waitFor } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'

const SampleButton = () => {
  const [value, setValue] = useState(0)
  const increment = () => {
    setValue(prev => prev + 1)
  }

  return (
    <>
      <button
        type="button"
        onMouseEnter={() => {
          increment()
        }}
        onClick={() => {
          increment()
        }}
      >
        サンプルボタン
      </button>
      <span data-testid="value">{value}</span>
    </>
  )
}

test('クリックイベントの発火テスト', async () => {
  const user = userEvent.setup()
  const { getByRole, getByTestId } = render(<SampleButton />)
  await waitFor(() => user.click(getByRole('button')))
  expect(getByTestId('value')).toHaveTextContent('1')
})

結果は以下のようになり、テストが失敗します。

Error: expect(element).toHaveTextContent()

Expected element to have text content:
  1
Received:
  2

原因

user-eventはユーザが実際のブラウザで行うインタラクションを再現しています。
例えばユーザがボタンをクリックする際、カーソルをボタンの上に持ってきてマウスを左クリックします。
先ほどのuser.click()はこの動作を再現しています。
そのため、onMouseEnterも発火しており、最終的なvalueの値は2になりました。

解決方法

解決方法として、以下の2通りが考えられます。

userEvent.setup()にオプションとしてskipHover: trueを渡す

userEvent.setup()はオプションを引数に取れます。
skipHover: trueをオプションとして渡してあげると、カーソルをターゲット要素に移動させる挙動をスキップするようになります。

const user = userEvent.setup({ skipHover: true })

参考:https://testing-library.com/docs/user-event/options#skiphover

fireEvent.click()を利用する

user-eventの代わりに、fireEventのクリックを利用する方法です。
fireEventはDOMイベントを発火させるだけなので、fireEvent.click(element)とすることでクリックイベントのみを発火させられます。

fireEvent.click(getByRole('button'))

まとめ

user-eventは、ユーザが実際のブラウザで行うインタラクションを再現しているため、user.click()はホバーイベントやフォーカスイベントも発火させます。
これを回避するには以下の2通りが利用できます。

  • userEvent.setup()にオプションとしてskipHover: trueを渡す
    • ホバーイベントはスキップできるが、フォーカスイベントは発火する
  • fireEvent.click()を利用する
    • クリックイベントのみ発火する

以下余談

クリックイベントのテストコードを書いている際、user.click()なのになぜかホバーやフォーカスイベントまで発火している?となり、つまずいて数時間溶かしました、、、
ちゃんと公式ドキュメントを見ていれば「user-eventはユーザが実際のブラウザで行うインタラクションを再現している」こと、「skipHoverというオプションがある」ことを認識できていたはず、、

公式ドキュメント:https://testing-library.com/docs/user-event/intro

Discussion