Should I test that?

Should I test event handlers?

Verdict

Yes

Give each event handler one test that fires the real event on the rendered control and checks the result the user sees; do not test a handler by calling its function directly.

Why

Test event handlers minimally: one test per handler that fires the real event. In the typical case, a click handler in a web front end that calls one model method, Blast radius is users and Change frequency is regularly, because a dead button breaks a feature and handlers change with their screen. Detectability is same-day and Reversibility is trivial, because users report a dead control within hours and a revert fixes it with nothing stored wrong. Test cost is moderate, because the test renders the view in a DOM and changes when the markup changes.

When the decision changes
WhenDecisionWhy
The click handler submits a payment, and a double click can send it twiceTest mandatory: assert that a second click during submission sends no second requestBlast radius rises to money and Reversibility to costly, because a double charge needs a refund
A server-side handler sends the confirmation email when an OrderPlaced event is publishedTest: publish the event in a test and assert that the email is queuedDetectability moves to eventually, because an unregistered handler raises no error, and Reversibility to with-effort, because missed emails need a resend
The handler passes two values of one type, such as a start date and an end date, to a save callTest: enter a distinct value in each field and assert that each value reaches the right parameterDetectability moves to eventually, because a swap saves a plausible date, and Reversibility to with-effort, because the saved records need repair
A browser test on every CI run clicks the control with a distinct value in each field and checks the resultDo not add a separate test for the event handler; rely on the CI browser test that already clicks the controlDetectability drops to immediately, because a broken binding or a swapped value fails CI before merge, and Reversibility stays trivial
The event handler sits on an internal admin screen that only your team usesDo not test the event handler; click the control after each changeBlast radius falls to internal, because no customer sees a dead control
The event handler reacts to drag-and-drop or touch gestures that a test DOM cannot lay outDo not test the event handler; watch front-end error reports after each releaseTest cost rises to heavy, because the test needs a real browser, while Detectability stays same-day

What breaks if you don't test

An event handler breaks between the element and the function, where a direct call cannot see it. A renamed element ID leaves $('#saved-filters').on('click', ...) bound to nothing. A method passed without bind runs with this set to the element (MDN), so the click throws. Both bugs pass every test that calls the handler by name, and users find the dead control the same day.

What you lose if you over-test

The common over-test calls view.handleFilterClick() and asserts that a mocked model method was called. That test repeats the handler's one line, stays green when the binding breaks, and turns red when you rename the model method. A screen with twenty handlers gets twenty such tests, and none of them clicks anything.

How to test

Write one test per handler against the rendered view: render it into a DOM, fire the event the user fires, and assert what the user sees or the request that leaves the page. Use user-event with Testing Library, or .trigger() for jQuery code. Move logic such as a discount calculation into a function with its own unit tests. Skip error branches until a bug in one reaches a user, then add a regression test.

When the answer changes

  • The handler submits a payment or an order, and a double click can send it twice.
  • The handler subscribes to an application event on the server, where a missing subscription raises no error.
  • The handler passes two values of one type, such as a start date and an end date, to a save call.

Real incident + Code example

The checkbox that threw on every click

On a Backbone front end I worked on, a refactor moved the binding of a saved-filters checkbox from the view's events hash to a manual jQuery call. The handler's unit test called it by name and stayed green. jQuery calls a handler with this set to the element (jQuery docs), so every click threw a TypeError until a user reported the dead checkbox that afternoon:

// In render(); handleFilterClick calls this.filters.toggle('saved')
this.$('#saved-filters').on('click', this.handleFilterClick);

// Stays green: a direct call runs with this === view
it('toggles saved filters', () => {
  view.handleFilterClick();
  expect(filters.get('saved')).toBe(true);
});

// Fails: the click goes through the real binding
it('toggles saved filters on click', () => {
  view.render();
  view.$('#saved-filters').trigger('click');
  expect(filters.get('saved')).toBe(true);
});

We bound the handler to the view and replaced the direct-call tests with one .trigger() test per handler.

FAQ

Should JavaScript event handlers be unit tested?

Yes, test a JavaScript event handler once by firing the real event on the rendered element and asserting the result. A direct call to the handler function skips the binding and the value of this, which break without a build error.

Should I call an event handler directly in a test?

No, fire the event instead, so one test covers the element, the binding and the handler. Call a plain function directly only when it holds logic that you moved out of the handler.

Should I test domain event handlers?

Yes, test a domain event handler by publishing the event in a test and asserting the effect, such as a queued email. A handler that is never registered raises no error, so the missing effect shows up days later.

Should I unit test event handlers in C#?

Yes, test a C# event handler by raising the event, for example with Button.PerformClick() in Windows Forms, and checking the result. Keep the handler to one call into a class with its own unit tests.