torsdag 17 april 2014

Testing promises with Jasmine

Let's say I have this code (using lie.js for promises, but should work about the same with q.js or angulars $q):

  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

If I want to test if it works, I would write a spec. To make it easier to read I will include the code under test in the spec:


describe 'Foo', () ->
  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

  it 'should wait until promise resolves', () ->
    self = this

    Foo.bar()
    .then (baz)->
      baz.should.be true

I now watch it run with success:
 
Done, without errors. 

Cool, it now works. Or does it?

I change the test to something that should fail, like this;

'use strict'

describe 'Foo', () ->
  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

  it 'should wait until promise resolves', () ->
    self = this

    Foo.bar()
    .then (baz)->
      true.should.be false

If we run it, it still succeeds:
 
Done, without errors. 

Obviously not quite working...
The reason is still the same, the code in the 'then' clause never executes, because the test finishes to early.

I then add a waitsFor like this:

describe 'Foo', () ->
  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

  it 'should wait until promise resolves', () ->
    self = this

    waitsFor () -> self.hasRun

    Foo.bar()
    .then (aBool)->
      true.should.be false
      self.hasRun = true 
 
With the following result:
 
Done, without errors.  
 
Hmm, this is not what I expected. It should fail, right? The reason for this isn't quite obvious. Any errors in the then clause will be swallowed in the following catch clause. Which really isn't what I want. If I change to the following code:
 
describe 'Foo', () ->
  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

  it 'should wait until promise resolves', () ->
    self = this

    waitsFor () -> self.hasRun

    Foo.bar().then (baz)->
          self.baz = true
          self.hasRun = true

    self.baz.should.be true

There is still the same problem with TypeError: Cannot read property 'should' of undefined 

We have to surround it with a run(), which waits until the waitsFor is complete before executing:

describe 'Foo', () ->
  Foo = {bar:()->
    new Promise (resolve, reject)->
      resolve(true)
  }

  it 'should wait until promise resolves', () ->
    self = this

    waitsFor () -> self.hasRun

    Foo.bar().then (baz)->
          self.baz = true
          self.hasRun = true

    runs () ->
      self.baz.should.equal true


Done, without errors. 

And if I change the last line to something that should fail:
      false.should.equal true
 
AssertionError: expected false to equal true 
 
So it now runs the spec as expected. And in case you don't habla coffee, here is the same test written in Javascript:
 

describe('Foo', function() {
  var Foo;
  Foo = {
    bar: function() {
      return new Promise(function(resolve, reject) {
        resolve(true);
      });
    }
  };
  it('should wait until promise resolves', function() {
    var self = this;
    waitsFor(function() {
      return self.hasRun;
    });
    Foo.bar().then(function(baz) {
      self.baz = true;
      self.hasRun = true;
    });
    runs(function() {
      self.baz.should.be(true);
    });
  });
});  

Inga kommentarer:

Skicka en kommentar