Menu

While working on an Aurelia application, I found myself needing to unit-test a recurring function; a function that is called every so many seconds. This is called a timing event in JavaScript, and is achieved through the setTimeout and setInterval functions.

The setup

We have the following Home module, named home.js, defined in our Aurelia application.

Very little happens here other than some initialization. The main thing is that the activate function calls startLoop, which in turn sets up a loop that will call getRandomSuggestion every 7 seconds. So how do we test this?

Aurelia, Jasmine and ECMAScript 2015

It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.

And basically because it damn-easy to learn and work with and because it has good documentation. Speaking of which, the code examples in there aren't written in ECMASCript 2015 (ES2015 for short), but in the current, more familiar to everyone JavaScript syntax.

How do you get Jasmine to work with ES2015? I'm assuming that if you're reading this post you already have an Aurelia application set-up, and that it's probably based on the Aurelia-Skeleton-Navigation, but if you don't, or it isn't, go take a look at it since it contains a nice, basic unit-testing set-up using Jasmine and ES2015

We import the Home module, and, in the beforeEach, we initialize said module into the sut variable. S.U.T stands for System Under Test, and it's just a TDD/BDD way to refer to the module, class, etc. that you are testing.

If you've never worked with Jasmine before, beforeEach does exactly what it's name suggests: it is run before each of the tests defined in the current test module, and it is used to initialize classes, variables or mock data so that every test starts afresh, ensuring no test is dependent on any other test's results. There is also an afterEach method, which is used to clean up after each test has run.

The tests

In an ideal TDD/BDD scenario you'd be writing a specs/test first, making it fail, and then write just enough code to make it pass. For the sake of readability I've already shown you the Home module's code, but I'll follow the steps I originally took and, even for that simple bit of code, we'll have two tests.

First-off, we want to test that the loop is started and the best way to achieve this to "spy" on it. Jasmine spies are a way to mock an object or method within that object; Jasmine basically substitutes the original object or method with a spy object, thereby allowing the test framework to check the interactions with that object or method.

So, we want to verify that the method startLoop is called when the activate method is called, therefore, we "spy" on startLoop and then use one of the several handy methods that Jasmine spies provide: toHaveBeenCalled

done is just a callback that Jasmine allows you to pass to signal that some asynchronous task is completed. In our case it's necessary because activate returns a promise; without a call to done() our test would never finish.

Testing timing events

Testing methods that rely on the computer clock can be tricky, especially because you don't want to have to wait for the actual, real time to pass on every test run.

So how do we test that we are indeed fetching a new suggestion every so many seconds? Again, Jasmine to the rescue: Jasmine provides a clock API. When you call clock.install(), Jasmine substitutes the Window.setInterval and Window.setTimer methods with an implementation of its own. This allows Jasmine once again to check on calls to these methods and to simulate some of their functionality to manipulate time.

The first thing we do in our test is to set up Jasmine's clock by calling jasmine.clock().install(). This allows us to simulate the passage of time by calling jasmine.clock().tick, and passing it the amount of milliseconds we need the clock to move forward.

After initializing the clock, we want to spy on getRandomSuggestion; we'll then wind the clock 14 seconds forward. Since we want getRandomSuggestion to be called every 7 sec, we can expect the method to have been called twice.

Always remember to call jasmine.clock().uninstall() at the end of the test so that the original setInterval and setTimer functions are restored. If you have several tests depending on clock API, a much better approach is to install it in the beforeEach and uninstall it in the afterEach methods.

And there you have it. If you have any questions or suggestions let us know in the comments!