dispatchEvent pitfalls
Collection of common pitfalls when manually dispatch DOM events
One of the first mistakes I made when using enzyme
’s simulate()
was assuming that this is pretty much equivalent to what happens in a browser. After encountering a couple of very confusing test failures I learned that it’s basically just better to call the prop (component.props()['onChange'](event)
) directly1.
This approach gives you the amount of test coverage that most people would be satisfied with but gives you next to zero confidence that your component can respond properly to user events.
@testing-library/react
’s fireEvent
is slightly better since it creates proper events and requires the tester to use the actual event target. Unfortunately even if you have no layer between the DOM and your tests your tests will still report false positives.
The two main offenders I want to showcase is pressing a key and focusing an element. Both these events are important for accessibility testing since the keyboard is the main input device for visually impaired users and the focused element acts as a cursor.
dispatchEvent(focusEvent)
It’s pretty common to see something like
const { fireEvent, getByRole } = render('<Button />');
const button = getByRole('button');
fireEvent.focus(button);
expect(button).toHaveFocus();
What you’ll encounter in this case is failed test since your button
won’t have focus. Now a clever tester might just rewrite this spying on the onFocus
listener and merge your Button
implementation since the test passes. Unfortunately there’s a big difference between receiving a focus event and actually being focused. Firing a focus event will not actually move the focus in the browser you can try this by clicking
The second button shouldn’t have an outline since it isn’t focused. Now in the second example we have the same setup but as you’ll see
the area is focused!
With a very minor adjustment we receive focus events and move focus:
-button.dispatchEvent(new FocusEvent('focus'))
+button.focus()
You even have to write less code (even though most libraries will have a event factory).
But HTMLElement.focus() has another powerful benefit over dispatching focus events and listening to them: It checks if the element is actually focusable.
This is especially important if you have to (re-)implement custom widgets and forget to add tabindex
or add it to the wrong element. Not adding a tabindex
will render those elements useless for assistive technology.
dispatchEvent(keydownEvent)
I’ll skip the intro this time: Don’t dispatch keydown events on queried elements. You’re cheating and I’ll explain you how: Your browser will determine the target of your keydown events by checking the currently focused element i.e. document.activeElement
2.
Applying
-element.dispatchEvent(keydownEvent)
+element.focus()
+document.activeElement.dispatchEvent(keydownEvent)
has two benefits:
- You test if your element is actually focusable
- Your code hits all the code paths an actual user would also hit
Summary
It comes down to looking for lines in your code where the following diffs can be applied:
-button.dispatchEvent(new FocusEvent('focus'))
+button.focus()
-element.dispatchEvent(keydownEvent)
+element.focus()
+document.activeElement.dispatchEvent(keydownEvent)
Bonus
If you dispatch a change event you almost always want to include the new value. Otherwise your element will never receive the change event.
Sources
This material is based on https://testing-library.com/docs/guide-events#docsNav. The parts about focus and keydown were originally written by me. I wanted something easy to write as my first article on my page :)
https://github.com/airbnb/enzyme/issues/1999#issuecomment-460155725
The event target of a key event is the currently focused element which is processing the keyboard activity.
— https://www.w3.org/TR/uievents/#events-keyboard-event-order