React Testing Library では fireEvent よりも userEvent を使ったほうがいいらしい
ここに書いてあった
Whenever possible, use userEvent over fireEvent when using React Testing Library. At the time of writing this, userEvent doesn't include all the features of fireEvent, however, this may change in the future.
React Testing Library を使う際には、可能な限り fireEvent よりも userEvent の方を使ったほうがいいよ。
ただし、現時点において fireEvent にできて userEvent にできないこともあるよ(今後の開発に期待してね)。
みたいなことを書いてる。
詳しく調べてみる。
環境
@testing-library/react: v13.4.0
@testing-library/user-event: v14.4.3
fireEvent
冒頭に @testing-library/user-event 使ったほうがええぞって書いてる。
Most projects have a few use cases for fireEvent, but the majority of the time you should probably use @testing-library/user-event.
fireEvent
という API が提供されていて、これを使ってイベントを発火させる。
fireEvent[eventName](node: HTMLElement, eventProperties: Object)
DOM のイベントを発火させるメソッド。
第一引数に対象となるノード、第二引数にイベントのプロパティを指定する。
ここに列挙されたイベントを使える。
createEvent によって、それぞれのイベントを表現するオブジェクトと fireEvent の第二引数で指定した値からイベントが生成される。
イベントの発火の処理は dispatchEvent を使ってる。
参考
fireEvent (続き)
createEvent
という API も提供されていて、好きなようにカスタマイズしたイベントを生成できる。
サンプルにあるように、複数の DOM に対して同じイベントを走らせたいときにはいいかもしれない
const simulateImageInput = (inpout) => {
fireEvent(
input,
createEvent('input', input, {
target: {files: inputFiles},
...init,
}),
);
};
とはいえこれは次のように書いても実現できるので createEvent
って結局いつ使うの?という気持ちになった。
const simulateImageInput = (inpout) => {
fireEvent.upload(
input,
{
target: {files: inputFiles},
...init,
},
);
};
user-event
ブラウザでインタラクションによって生じるイベント発火させてシミュレーションをするための Testing Library 付属のライブラリ。
fireEvent との差分
以下のようなことが書かれている。
fireEvent は DOM のイベントを発火させるだけの軽量なラッパーで、ブラウザの低レイヤの API である dispatchEvent
を使ってイベントの発火を実現している。
一方、ブラウザは一つのインタラクションに対して2つ以上のイベントを発火させることもある。(たとえばテキストボックスに文字を入力する場合、 input の DOM にフォーカスをして、キーボードと input のイベントが発火して、値がタイプした通りに変化する)。
user-event を使うと、具体的なイベントではなくユーザインタラクションそのものを記述できる。
しかもそのインタラクションの途中で、その DOM のブラウザでの表示状態やインタラクションの状態の判断もやってくれる。
つまり単一のイベントを発火させる fireEvent を使ってインタラクションに応じていくつものイベントを扱うブラウザの挙動を再現するのが難しいというのが問題点らしい。
そして、 より実際のブラウザに近い挙動を再現できる user-event の利用が推奨されている。
ただし、いくつかはまだ挙動が再現しきれていないので、そういう場合には fireEvent を使って頑張ってくれってことらしい。
fireEvent と user-event で差が出る例
ここで言及されている。
onKeyDown
のイベントが preventDefault
によってキャンセルされて input に伝わらなくしたような場合においても、 fireEvent
で input に直接イベントを発火させる方法なら入力させられる。
test("KeyDown させないラッパーで囲っても、テキストフィールドに fireEvent で値を入れるとノードの value に反映される", async () => {
render(<MyTextField canType={false} />);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "bar" } });
expect(input).toHaveValue("bar");
});
一方、userEvent を使って input にフォーカスしてからキーボードをタイプするという実際のブラウザを使ったユーザインタラクションに近い挙動でテストすると、 input に値を入力できないことも再現できている。
test("KeyDown させないラッパーで囲っていると、 input をクリックしてテキストを入力してもノードの value に反映されない", async () => {
render(<MyTextField canType={false} />);
const user = userEvent.setup();
const input = screen.getByRole("textbox");
await user.tripleClick(input);
await user.keyboard("bar");
expect(input).toHaveValue("foo");
});
use-event の中身を見てみると実際に要素に対してインタラクションを発生させてた場合を再現しているのがわかる。
たとえば、ダブルクリックのインタラクション(dblClick
)では「対象を element
とし、マウスの左クリックを2回実行」している。
また、文字列の入力のインタラクション(keyboard
)では「text
を適切に字句ごとに分割して、一つずつキーボードで入力」していることも読み取れる。
use-event は再現できていないインタラクションもあるとあったけど、うまく再現するのが難しいパターンに関してはやってないということかなあと思った。
user-event で実装されてないインタラクションとして、 Issue に次のようなものがあった(2022/9/18時点)
など
出展