user-eventのclick()はホバーも(フォーカスも)再現する
はじめに
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
というオプションがある」ことを認識できていたはず、、
Discussion