How-tos and Support

Using Jasmine for Asynchronous Testing

15 minutes to read

https://fast.wistia.com/embed/medias/m85fdxype3.jsonphttps://fast.wistia.com/assets/external/E-v1.js

This is the 18th Testing Tuesday episode. Every week we will share our insights and opinions on the software testing space. Drop by every Tuesday to learn more! Last week we spied on JavaScript methods with Jasmine.

Sign up for a free Codeship Account

Testing asynchronous JavaScript operations

In the last two Testing Tuesday episodes we already got familiar with Jasmine. If you haven't already, watching Testing Tuesday #16 will make it easier to follow along this episode.

JavaScript is the perfect language for asynchronous operations. But although they make our apps fast, elegant and responsive, they are also a challenge to test.

Jasmine provides two strategies for testing asynchronous operations:

  1. Waiting for certain events to occur until a timeout.
  2. Mocking the JavaScript clock and simulating that time passes.

In this screencast we cover both strategies and show you which one is appropriate for which situation.

Up next Testing Tuesday: Testing node.js applications with Jasmine

So far we've used Jasmine only in the web browser. Next week we'll show you how to test your Node.js applications with Jasmine. If you've got any questions or suggestions, please leave us a comment!

Further info:

Transcript

Testing asynchronous JavaScript with Jasmine

Intro

Ahoy and welcome! My name is Clemens Helm and this is Codeship's Testing Tuesday number 18! At the moment I do a lot of front end work at the Codeship and I'm falling in love with JavaScript all over again. That's why this episode is once more about testing Javascript with Jasmine. Last week we spied on methods to find out if and how they got called. This week we tackle one of the trickiest parts of JavaScript applications: Testing asynchronous operations.

Screencast

In the last two Testing Tuesday episodes we already got familiar with Jasmine. If you haven't already, watching Testing Tuesday #16 will make it easier to follow along this episode. I'll link to it in the further info section.

JavaScript is the perfect language for asynchronous operations. But although they make our apps fast, elegant and responsive, they are also a challenge to test. Jasmine lets us test asynchronous applications by waiting for certain events to occur until there's a timeout.

This week we're developing an application for racing robots. We've got a Robot that stands at 0 meters.

LANG:code
function Robot() {
this.meters = 0;
this.motion = "stopped";
}

We can make it run a few meters though. The robot moves at variable speed. It will make for sure 1 meter per second, but it can also be much faster. We represent this variation with Math.random(). When we call the runMeters method, the robot will start running immediately. After the time it needs to run it will stop at the specified position.

LANG:code
Robot.prototype.runMeters = function (meters) {
var timeToRun = meters * 1000 * Math.random();
robot = this;
robot.motion = "running";
setTimeout(function () {
robot.motion = "stopped";
robot.meters = meters;
}, timeToRun);
}

But how can we test this behavior? Let's try it as usual:

LANG:code
describe("Robot", function() {
it("should run 5 meters and stop", function () {
robot = new Robot();
robot.runMeters(5);
expect(robot.motion).toBe("stopped");
expect(robot.meters).toBe(5);
});
});

Jasmine complains that both of our conditions aren't met:

LANG:code
Expected 'running' to be 'stopped'.
Expected 0 to be 5.

The problem is that it takes some time for the robot to reach its destination, but we check the expectations right after it started running. But how can we check our expectations once the robot stopped again? Jasmine provides a waitsFor method to wait for events to happen:

LANG:code
it("should run 5 meters and stop", function () {
robot = new Robot();
robot.runMeters(5);
waitsFor(function () {
return robot.motion == "stopped";
}, "The robot should stop", 5000);
runs(function () {
expect(robot.meters).toBe(5);
});
});

So we've got two block here: waitsFor and runs. These blocks will be executed subsequently. waitsFor expects a function with a condition, an error message and a timeout. It will poll until the condition is met or the timeout expires. If the timeout expires, the spec fails with the error message given. We can try this by passing a very little timeout:

LANG:code
waitsFor(function () {
return robot.motion == "stopped";
}, "The robot should stop", 100);

Now we get the error message

LANG:code
timeout: timed out after 100 msec waiting for The robot should stop

When the waitsFor condition is met, the spec procedes with the next runs block. In our case, this will check if the robot has run 5 meters. Let's change the timeout to 5 seconds again and run the specs

LANG:code
waitsFor(function () {
return robot.motion == "stopped";
}, "The robot should stop", 5000);

And now our spec succeeds!

But couldn't we have written the spec simply with a setTimeout call like this?

LANG:code
it("should run 5 meters and stop", function () {
robot = new Robot();
robot.runMeters(5);
setTimeout(function (){
expect(robot.motion).toBe("stopped");
expect(robot.meters).toBe(5);
}, 5000);
});

This doesn't work, because the spec finishes before the callback function is invoked. But even if it worked, it would be much slower: Maybe it took our robot just a few milliseconds to run 5 meters, but we would still need to wait 5 seconds. waitsFor polls for the condition all the time and procedes immediately as soon as it's true. When your spec suite grows, this will make a tremendous difference to your spec execution time.

Another option is to travel through time with Jasmine's clock mock:

LANG:code
it("should run 5 meters and stop", function () {
jasmine.Clock.useMock();
robot = new Robot();
robot.runMeters(5);
jasmine.Clock.tick(5000);
expect(robot.motion).toBe("stopped");
expect(robot.meters).toBe(5);
});

This will mock the JavaScript time, so it's a great way to make setTimeout functions start sooner. In our case this works very well: The clock lets 5 seconds pass and our robot has reached our destination immediately. However, since the clock mock overrides the global JavaScript time, this can lead to unexpected behavior.

This approach also only works, when you are testing a self-contained application. As soon as you have to wait for external dependencies like a database query or an AJAX call, mocking the time won't work anymore, because time still runs normally outside of your JavaScript runtime.

Outro

So Jasmine is a great tool for testing asynchronous operations. Next week we'll take a look at testing your Node.js applications with Jasmine. Stay tuned and – most of all – always stay shipping!

Subscription confirmed

You'll now receive our latest news, releases, and event updates by email.