fireEventではなくuser-eventを使う
fireEvent()ではなく@testing-library/user-eventを使う
@testing-library/user-eventの方がユーザの実際の行動の再現に近いから。
fireEvent()が非推奨ってわけではない
両者の違い ref
fireEvent()は、指定したDOM Eventのみを発火させる
内部実装は、ただdispatchEvent()をラップしただけのもの ref
@testing-library/user-eventはよりユーザの実際の操作に近い挙動をする
実際のインタラクションで生じるような複数のeventを全て発火させる ref
ユーザの操作自体をmockした感じ
ユーザが何らかのアクションを起こしたときに、複数のEventが生じることはよくある
例えば、「fieldに文字を入力する」という動作をするとき、生じているeventは
inputのonChangeだけではなく、
inputにfocusするだとか、
keyboardを押下するだとか、
いくつかのeventが複合的に生じている
差が生じる例
この記事のコードを一部改変した
以下のようなComponentをテストする
code:ts
function InputComponent() {
const value, setValue = useState('');
return (
<div onKeyDown={e => e.preventDefault()}>
<input value={value} onChange={e => setValue(e.target.value)} />
</div>
);
}
間違えて、inputの親でkey down操作をブロックする操作を書いてしまっている
inputの内容を書き換えられないので「入力できる」というテストではfailしてほしい
fireEventで以下の様に書くとpassしてしまう
code:ts
test('fireEvent example', async () => {
render(<InputComponent />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'new value' } });
expect(input).toHaveValue('new value');
});
user-eventを使うとちゃんとfailする
code:ts
test('userEvent example', async () => {
const user = userEvent.setup()
render(<InputComponent />);
const input = screen.getByRole('textbox');
await user.type(input, 'new value');
expect(input).toHaveValue('new value');
});
逆に、敢えてfireEvent()を使うべき場合があるとすればいつか?
user-eventはまだ挙動を正しく再現しきれていないケースがある
この辺のissueがそう
こういうケースではよりprimitiveなfireEvent()を組み合わせてテストするしかない
と、docsに書かれている
テストの内容を敢えて絞りたい、というケースはありそうmrsekut.icon
生じるeventが複合的すぎると、1つのtest caseの中で、複数の振る舞いをチェックすることになってしまう
多機能なInput Componentを作った際に、個々のeventに対してunit testingしたい場合など
逆に、そうでない場合は、普通にuser-event使えば良いと思う
まあ、特にE2Eの場合は、ほぼ必ずuser-eventの方が適していそう
パフォーマンス観点
これもtest caseの話と近い
明らかにuser-eventの方が処理内容が多いので、fireEventと比較すると、動作が重くなる
多量の入力を与えて個々の機能をテストしたい際に、要らん箇所のテスト処理に重くなるのは避けたい
でも、これもまあほぼ問題になることはないだろうし、問題になってから検討すれば良い話だろうmrsekut.icon
Testing Libraryのテストは fireEvent より user-event を使おう - 駄文日記 2nd side
「簡潔に書ける」というの理由として微妙では?
fireEventは別にact()で包む必要はない
本題とはあまり関係ないが、差が生じる別の例
input内の内容を書き換えるときに、初期値が"default"としたときに
fireEventで以下のように書くと、
fireEvent.change(input, { target: { value: 'new value' } });
fieldの内容は"new value"になる
user-eventで以下の様に書くと
await userEvent.type(input, 'new value');
fieldの内容は"defaultnew value"になる
そうだね、って感じだけど、書き換えていく機会があるとちょいハマりしそう