1

I've come across a case where I simply can't make the test passed.

Here is the test:

it('should accept an fucntion run it immediately', inject(function($rootScope, ready) {
    var spy = jasmine.createSpy('ready').andCallFake(function(){
        console.log("I'm ready");
    });
    ready.done(spy);
    expect(spy).toHaveBeenCalled();
}));

Here is code:

angular.module('myApp').factory('ready', function($q, _, $rootScope){

    var defer = $q.defer();

    //in real case it's actually calling 3rd party code, so no $timeout
    _.defer(function(){
        $rootScope.$apply(function(){
            defer.resolve();
        });
    });

    return {
        done: function(fn){
            defer.promise.then(fn);
        }
    };
});

The log of I'm ready did appeared but the test still failed. So I think its just the problem of handling async flow in jasmine. But I can't think of to test it using jasmine's runs and waitsFor.

jackysee
  • 2,051
  • 4
  • 32
  • 38
  • See this answer: http://stackoverflow.com/questions/15477370/unit-testing-an-asynchronous-service-in-angularjs – Maxim Shoustin Jan 09 '14 at 07:59
  • A possibly simplification: why not just return `defer.promise` from the factory? Then the calling code can use `ready.then(function() {...})`, otherwise you're going into slightly back towards callback-hell. – Michal Charemza Jan 09 '14 at 08:05
  • @MichalCharemza that would be good too – jackysee Jan 10 '14 at 06:08

1 Answers1

1

I suspect the test is failing, and then the console.log appears, in that order, as the promise isn't resolved until after the _.defer has called its callback, which is after the current call stack is cleared.

What you need is a way to force the underscore/lodash _.defer, to call its callback/promises, so your promise in the factory gets resolved immediately. If you were using $timeout, you could call $timeout.flush() in the test, but as far as I know underscore/lodash doesn't have anything like this.

However, before the test, you should be able to inject a mock '_', with a defer function, that calls its callback immediately:

beforeEach(module(function($provide) {
  $provide.value('_', {
    'defer': function(callback) {callback()};
  });    
}));

In your real case, you would need to do something like the above, by injecting a mock 3rd party service and forcing it to complete, so your promise gets resolved before the expect call in the test.

If for some reason you can't inject a mock version of the 3rd party code, you can make the test asynchronous, as explained at http://pivotal.github.io/jasmine/#section-Asynchronous_Support, but this should be avoided if possible, as it makes the test runs slower.

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
  • 1
    It only works if I add a $rootScope.$apply() before expect check. – jackysee Jan 10 '14 at 07:40
  • 1
    You're right! I had just [made this Plunker](http://plnkr.co/edit/LPsuH5I6J0BsjRVdKEvD?p=preview) to work that out – Michal Charemza Jan 10 '14 at 08:54
  • dear @MichalCharemza I am struggling with the similar issue, but cant figure it out. Can you please check this question? http://stackoverflow.com/questions/27921918/how-to-test-defer-using-jasmine-angularjs – Max Jan 13 '15 at 14:06