8

I'm trying to use Jasmine 2.0 to write unit tests for some logic in an AngularJS app, but the logic is inside an event listener. From the controller:

  window.addEventListener('message', function(e) {
    if (e.data === "sendMessage()") {
      $scope.submit();
    }
  }, false);

And from the test file:

  describe("post message", function() {
    beforeEach(function(done) {
      var controller = createController(controllerParams);
      spyOn($scope, 'submit');
      window.postMessage('sendMessage()', '*');
      done();
    });

    it('should submit on a sent message', function (done) {
      expect($scope.submit).toHaveBeenCalled();
      done();
    });
  });

But the test fails, the spy never being hit. Extra info from putting in console debug statements:

  • window.addEventListener in the controller IS getting called.
  • The beforeEach and it block are both getting called.
  • The above message handler in the controller is not getting called during the test.
  • The message sent in this test is eventually being receievd by the message handler, several times, but not until after the test ends.

What is my test missing here?

Alan Gordon
  • 103
  • 1
  • 5
  • Aren't you supposed to call postMessage on *another* window than the one that will receive the event ? (say, creating a popup, as is done on https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage?redirectlocale=en-US&redirectslug=DOM%2Fwindow.postMessage ) – phtrivier Jun 12 '15 at 08:32
  • Tried changing it to a postmessage from a popup off of window instead of a postmessage from window, but all that changed is that, instead of receiving the message too late, it doesn't receive the message at all. – Alan Gordon Jun 12 '15 at 13:19
  • @AlanGordon: Did you already find a solution for this? I'm having the same issues ;-) – Michael Hunziker Jun 19 '15 at 12:18

1 Answers1

7

It is happening because in your beforeEach block you call window.postMessage() (which is asynchronous and you don't know when it's gonna execute) and then you call done() right after it as it would be synchronous code. But window.postMessage() as async, and basically you need to call done() when your async operation is complete. It could be done like this:

beforeEach(function(done) {  
    spyOn(someObject, 'submit').and.callFake(function () {
      done();
    });
});

So when your spy executes, then async operation is considered complete.

This could be expressed even shorter:

beforeEach(function(done) {  
    spyOn(someObject, 'submit').and.callFake(done);
});

Here is the full code:

var someObject = {
  submit: function () {
    console.log('Submit');
  }
};

window.addEventListener('message', function(e) {
  if (e.data === "sendMessage()") {
    someObject.submit();
  }
}, false);

// Test

describe('window.postMessage', function () {

  beforeEach(function(done) {  
    spyOn(someObject, 'submit').and.callFake(function () {
      done();
    });
    window.postMessage('sendMessage()', '*');
  });

  it('should submit on a sent message', function () {
    expect(someObject.submit).toHaveBeenCalled();
  });

});

See a working JS Bin: http://jsbin.com/xikewogagu/1/edit?html,js,console,output

I did not use Angular in this sample because it is reproducible with a pure JS.

Michael Radionov
  • 12,859
  • 1
  • 55
  • 72