Fixing Tests and Global Events in Ember.js

Testing in Ember.js has never been easier, and with the Ember-CLI generators, you get simple tests for free whenever you create a new component, controller, route, or anything, really! For example, we can run

$ ember g component foo-bar

and not only will Ember-CLI generate a component class and its template, it’ll also give you a simple integration test.

As you develop your shiny new component, you’ll probably want to expand upon your tests, too, though this first one looks pretty much prefect, right? This was my thinking before making a component that attached events outside of its context. Specifically, a component that needed to listen to click events that happen outside of itself, so it knows it is no longer needed and could close. This is common behavior in tooltips, popups, and other UI features.

Adding this functionality is fairly trivial in Ember, especially if you make use of ember-click-outside. Because we’re good citizens (and we’re not fans of memory leaks), we dutifully remove the event listeners upon component destruction, ensuring nothing is left behind. This all works great in the normal app environment, and I’ve made an Ember Twiddle showing that it works when the component is duplicated, too.

https://ember-twiddle.com/fc5dd5d2cf970d4c3cd944f88c77d6c5

And if you run the simple included test, everything appears to pass nicely.

That’s what we like to see.

However, if you click anywhere on the test page, something strange happens.

It’s a disaster!

Looks like there’s still an event attached to the window, and this will cause problems if you have multiple component tests that implement this behavior. So what happened here?

You may remember that the first test that was generated for our component rendered the component with and without block content in a single test. It appears that the teardown references only one of the event handlers, leaving the other orphaned on the page. (if you have more information on why this happens, please let me know!)

Fortunately, the fix is pretty simple. We’ll make sure that the component is only rendered once per test. This gives us the following — safer — tests.

The above layout ensures the global events are set up and removed correctly every time.

Summary

If you’re making components that register events outside of Ember’s ecosystem, you should be sure to remove them in your willDestroyElement life cycle hook. In your component integration tests, be sure to only render the component once per test so the events can be removed correctly. This will lead to happier tests for all.