Using Jasmine for Asynchronous Testing

Written by: Clemens Helm

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.


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.

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.

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:

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:

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:

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:

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

Now we get the error message

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

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?

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:

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!

Stay up to date

We'll never share your email address and you can opt out at any time, we promise.