Asynchronous Unit Tests With Mocha, Promises, And WinJS

Before I get in to the guts of this post, you need to read Christopher Bennage’s post on how we have our Mocha test suite set up for our Win8 / WinJS project. It’s not the best setup, but it works. It allows us to write tests for our app, even if it is a little bit painful to constantly link files in to the test project. I have hopes that we’ll find a better solution for this, but for now, we’re moving forward with this because it works.

I’ve already talked about how you need to understand promises in order to build Win8 / WinJS applications. It’s an absolute must because there is almost no way you can build an app without using them. Anything you might build that doesn’t use them would likely be very trivial and not very useful, honestly. They are baked in to the WinJS framework very deeply and I see this as a good thing. But understanding how to use a promise is very different than understanding how to write tests for code that uses promises. 

Asynchronous Mocha Tests

Testing a promise means that you’re testing asynchronous code. You have no knowledge of when the asynchronous code will finish. Because of this you can’t just assign some value to a variable in the async callback and hope your test will catch it. This can be demonstrated easily by using a call to “setTimeout” within a Mocha test, using Chai as the expect framework:

describe("a test", function(){
  var foo = false;

  beforeEach(function(){

    // simulate async call w/ setTimeout
    setTimeout(function(){
      foo = true;
    }, 50);

  });

  it("should pass", function(){
    expect(foo).equals(true);
  });

});

Now you might think that this test should pass. When the “expect” is run inside of the specification, it should verify that the “foo” variable is set to true because the code in the setTimeout call sets it to true. But the test fails because the expectation is run before the callback in the setTimeout is called. 

To correct this, Mocha can provide a “done” callback method that you can call from the beforeEach call, afterEach call, it calls, and more. This “done” parameter, when present in your callback function, tells Mocha that you are writing an asynchronous test. This causes mocha to enter a timer when the function with the “done” parameter runs, waiting for the async function to finish – which is facilitated by either calling the `done()` function, or by exceeding a 2 second timeout. 

The basic idea behind the `done()` call is that you call this after your async code has completed, and your test has modified everything it needs to modify, so you can check the results correctly.

With that in mind, we can update our test to use the done call:

describe("a test", function(){
  var foo = false;

  beforeEach(function(done){

    setTimeout(function(){
      foo = true;

      // complete the async beforeEach
      done();

    }, 50);

  });

  it("should pass", function(){
    expect(foo).equals(true);
  });

});

And now the test passes. We’re telling Mocha to wait for the async code to complete before it runs the expectations. When the timeout finally fires, we set our “foo” variable to true and then call `done()`. This tells Mocha that the async test is finished and it moves on to the expectations. 

Testing The Value In A Promise’ Callback

When a promise resolves, it often provides a value as a parameter to the callback function. This allows you to get the information that you requested in an asynchronous manner. And it’s often the case that the value of this async callback needs to have assertions or expectations set against it. There are a couple of ways that this can be done, depending on your specific scenario and the code you’re trying to test.

The first way is to add your “then” callback in the beforeEach of the test. You would assign the value from this function to a variable that is set up in the “describe” function. Then your expectations can check the value.

describe("a test", function(){
  var foo = false;

  beforeEach(function(done){

    var promise = new WinJS.Promise(function(complete){
      complete(true);
    });

    promise.then(function(value){

      // get the value from the completed promise
      foo = value;

      // complete the async beforeEach
      done();
    });

  });

  it("should pass", function(){
    expect(foo).equals(true);
  });

});

This works well for very simple scenarios and I generally recommend doing this when you can. It’s the easiest way to test the result of the promise. However, that’s not always an option. Sometimes a test is more complicated, has more expectations that it needs to verify, etc. In more complex situations you might find that the number of variables you have to create in the describe block is getting out of hand, or that the beforeEach function is doing too much work – work that should legitimately be in the expectations. In this scenario, you can move the async test and the “done” call in to the expecation’s “it” function. But it comes with a small trick that you need to do.

Expectations In Promises Are Problematic

Moving the async “done” call to an expectation is easy. You just add the “done” parameter to the “it” function and call it – the same as you would do in the beforeEach function.

describe("a test", function(){

  // ...

  it("should pass", function(done){

    // simulate async expecation
    setTimeout(function(){

      // complete the async expectation
      done();

    }, 50);

  });

});

But testing a WinJS promise with Mocha introduces a small complexity. When you call the “expect” function and the expectation fails, an exception is thrown. Normally, Mocha intercepts this exception from the expectation and reports it as a test failure. This is how Mocha works and it works well… until we introduce WinJS promises.

The problem with promises is that they intercept the expectation’s error throwing for us, preventing Mocha from seeing the error. For example, in this test, we should see a message saying it expected “false” to be “true”. What we get instead is a timeout error saying the test exceeded 2 seconds (2000ms). 

describe("a test", function(){
  var foo = false;
  var promise;

  beforeEach(function(){
    promise = new WinJS.Promise(function(complete){

      // complete the promise with a value of false
      // so that the expectation fails
      complete(false);

    });
  });

  it("should pass", function(done){

    promise.then(function(value){

      // expect the value to be true
      // which should fail with a message
      // saying it expected false to be true
      expect(value).equals(true);

      done();
    });

  });

});

When the promise completes, a value of false is passed in as the parameter of the “then” function. The expectation throws an error because it expects false to be true. This is all good so far. We expect this to fail. The problem comes in when the exception is thrown, though. Normally, the exception would be caught by the Mocha test runner which would cause the test to fail and Mocha would report the failure. But in this case, the promise is catching the error for us. That means the error never makes it out to Mocha. Throwing an error also means the “done()” function is never called, so the test goes in to a wait state and Mocha times out at 2 seconds, giving us a report of the testing timing out instead of the message from the failed expectation.

Done, Done, Done, Done: Async Expectations With WinJS Promises

To fix this problem, we need to take advantage of how WinJS promises work. According to the WinJS promises documentation and the Promises/A Spec that WinJS promises implements, throwing an exception in a “then” function causes the error to be forwarded to the error handling function of the promises “done” function. In other words, we can get access to the error that the expectation threw by chaining a “done” call on to the end of the “then” call:

promise
  .then(thenCallback)
  .done(thenCallback, errorCallback, completeCallback);

Ok, this is going to be a bit confusing. The “promise.then().done()” call is the promise’s “done” function. But Mocha also provides a “done” callback to the beforeEach and it functions… So there’s a lot of “done” in the upcoming discussion. I’ll try to keep things clear as much as I can.

At this point, we need to complete the async expectation. But we don’t want to call Mocha’s “done()” callback as-is because that would tell Mocha that the test passed. Instead, we need to call Mocha’s “done” callback with a parameter – the error that we caught. Passing a parameter to the Mocha’s “done” function – any parameter – tells Mocha that the test failed. It assumes that the parameter you pass in is the error from the expectation or the reason that the test failed. That means we can call Mocha’s “done” function with the “error” parameter of our error handling callback:

describe("a test", function(){
  var foo = false;
  var promise;

  beforeEach(function(){
    promise = new WinJS.Promise(function(complete){

      // complete the promise with a value of false
      // so that the expectation fails
      complete(false);

    });
  });

  it("should pass", function(done){

    promise.then(function(value){
      expect(value).equals(true);
      done();

    }).done(null, function(error){

      // the expectation threw an error
      // so forward that error to Mocha
      done(error);

    });

  });

});

Note the “null” first parameter of the promise’s “done” call. This is there because the “done” method takes 3 parameters as noted in the previous code snippet.

And we can make the code even more simple than this. Since the error handling callback of the promise has the same method signature of Mocha’s “done” callback, we can get rid of the inline function and pass “done” as the callback directly:

describe("a test", function(){
  var foo = false;
  var promise;

  beforeEach(function(){
    promise = new WinJS.Promise(function(complete){

      // complete the promise with a value of false
      // so that the expectation fails
      complete(false);

    });
  });

  it("should pass", function(done){

    promise.then(function(value){

      expect(value).equals(true);
      done();

    }).done(null, done); // <<-------

  });

});

Lastly, we still need to call Mocha’s “done()” from within our promise’s “then” function. This tells Mocha that the test is complete in cases where the expectation is met (the test passes).

Now you might be tempted to pass Mocha’s “done” as a parameter to the promise’s “then” and “error” callbacks of the promise’s “done”, like this:

  it("should pass", function(done){
    promise.then(function(value){
      expect(value).equals(true);
      done();

    }).done(done, done); // <<---- FAIL

  });

That won’t work, though, because the “then” callback receives a parameter from the promise and this would forward the parameter to Mocha’s “done” function, which would be interpreted as a test failure. So we have to keep Mocha’s “done()” call for a successful test in an inline callback of the promise’s “then” function, with our expectation.

With this in place our test passes and fails correctly. We get the error message that says it expected false to be true when we complete the promise with a value of “false”, and we get a passing test when we complete the promise with a value of “true”.

(… yeah, that’s a lot of “done” calls in our expectation, but it works. Hopefully that made sense.)

Async Tests: Problem Solved

As you can see, promises can pose a small challenge for unit tests. But they aren’t impossible or really that difficult to test. It just takes a little bit of understanding – how promises work, and how Mocha works. With that bit of knowledge, it’s pretty easy to get the tests to pass and fail correctly. The best part of this, though, is that you now have all the tools you need to do asynchronous testing with or without promises. You know all of the traps to look for, and all of the patterns to implement – whether it’s a WinJS promise, a jQuery AJAX call in a browser, a setTimeout call in your JavaScript code somewhere else, or any other asynchronous code in NodeJS or any other JavaScript runtime.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Async, ChaiJS, CommonJS, Javascript, MochaJS, Testing, WinJS. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.facebook.com/mohamedmansour Mohamed Mansour

    Great article Derick!

  • http://domenicdenicola.com/ Domenic Denicola

    My comment got deleted?

    • http://mutedsolutions.com Derick Bailey

      I never saw a previous comment from you. It probably never posted.

      • http://domenicdenicola.com/ Domenic Denicola

        OK, pretty sure Disqus was auto-deleting it because it contained two links. I worked around this by posting once without links, then editing the post to contain the links. It seems to still be there.

        • http://mutedsolutions.com Derick Bailey

          ok, cool. that looks great! thanks for the links :)

  • http://domenicdenicola.com/ Domenic Denicola

    So I wrote two libraries to make this whole thing much easier: Mocha as Promised, to take care of all this wiring, and Chai as Promised, which gives you fluent assertions about promises and their eventual fulfillment values or rejection reasons.

    You can then write tests like

    it(“should be fulfilled with 5″, function () {
    return promise.should.become(5);
    });

    it(“should return a user with the correct ID”, function () {
    return systemUnderTest.getUserAsync().should.eventually.have.property(“id”, “abc123″);
    });

    https://npmjs.org/package/mocha-as-promised
    https://npmjs.org/package/chai-as-promised

    • http://mutedsolutions.com Derick Bailey

      that looks awesome! … yeah, it’s probably filtering you for posting too many URLs. I don’t have any control over that, though :(

      maybe reply to your own comment here with one url per reply?

  • Will Duff

    You can reduce this

    var promise = new WinJS.Promise(function(complete){
    complete(true);
    });

    to this

    var promise = WinJS.Promise.as(true);

    • http://mutedsolutions.com Derick Bailey

      good call, Will. i’ve been using “as” a lot more recently. it’s very handy for these and other situations where you need to create a promise from a non-promise value :)

  • http://twitter.com/kimptoc Chris Kimpton

    Thanks – helped me understand done() better :)

  • Kevin

    Great article.