31

I've been trying to wrap my head around Jasmine 2.0 and AngularJS promises. I know that:

How can I test AngularJS promises using the new async syntax in Jasmine 2.0?

3
  • 1
    If testing promises, why do you need to use the async syntax at all, both in old and new Jasmine? Can you post a function that you're trying to test? For a lot of cases, you can use $timeout.flush(), and/or myPromise.resolve() in tests to force them to be synchronous. Commented Apr 19, 2014 at 9:14
  • 1
    @MichalCharemza To my surprise, $timeout.flush() works great without the use of Jasmine's done; I don't have a function to demonstrate when I would need done + $timeout.flush(). What if the tests use e.g. an actual $http backend? It's (obviously) better to mock $http for speed, but would $timeout.flush() without done work there? Does the delayed resolution of the promise make any difference? Commented Apr 21, 2014 at 3:01
  • 1
    If the tests use an actual $http backend, connecting to a real server, then the test would be asynchronous, and you have to use done. $timeout.flush() (or $httpBackend.flush()) affects the code running locally: you can't call a function to demand that a server respond to the request right now! If you're not sure about how to test a specific function (say, using $timeout or $http), then you can post that function in a question). Commented Apr 21, 2014 at 7:02

3 Answers 3

44
+100

After your call to promise.resolve():

  • Call $timeout.flush(). This will force a digest cycle and propagate the promise resolution
  • Call done(). This tells Jasmine the async tests have completed

Here's an example (Demo on Plunker):

describe('AngularJS promises and Jasmine 2.0', function() {
    var $q, $timeout;

    beforeEach(inject(function(_$q_, _$timeout_) {
        // Set `$q` and `$timeout` before tests run
        $q = _$q_;
        $timeout = _$timeout_;
    }));

    // Putting `done` as argument allows async testing
    it('Demonstrates asynchronous testing', function(done) {
        var deferred = $q.defer();

        $timeout(function() {
            deferred.resolve('I told you I would come!');
        }, 1000); // This won't actually wait for 1 second.
                  // `$timeout.flush()` will force it to execute.

        deferred.promise.then(function(value) {
            // Tests set within `then` function of promise
            expect(value).toBe('I told you I would come!');
        })
        // IMPORTANT: `done` must be called after promise is resolved
        .finally(done);

        $timeout.flush(); // Force digest cycle to resolve promises
    });
});
Sign up to request clarification or add additional context in comments.

5 Comments

Just wanted to add a link to K. Scott Allen's post on the beforeEach inject technique that you are using here: odetocode.com/blogs/scott/archive/2014/05/15/…
upvoted, seriously, only adding $timeout.flush() fixes my specs. really too bad this is not that well documented...
done is not needed above, all job is done by $timeout.flush()
Is there any reason to use $timeout.flush() instead of $rootScope.apply()? That is what the official documentation uses: docs.angularjs.org/api/ng/service/$q#testing
According to Pawel Kozlowski's reply on GitHub, using $timeout.$flush() will also clear timers. Most of the time $rootScope.$apply() works perfectly, but $timeout.$flush() covers more use cases
4

For me the $timeout.flush() didn't work very well, but I've multiple async calls in my spec. I found the $rootScope.$apply(), as a method to force the digeston each async call.

describe('AngularJS promises and Jasmine 2.0', function () {
  beforeEach(inject(function (_$q_, _$timeout_, _$rootScope_) {
    $q = _$q_
    $timeout = _$timeout_
    $rootScope = _$rootScope_
  }))

  it('demonstrates asynchronous testing', function (done) {
    var defer = $q.defer()

    Async.call()
    .then(function (response) {
      // Do something

      var d = $q.defer()
      Async.call()
      .then(function (response) {
        d.resolve(response)
        $rootScope.$apply() // Call the first digest 
      })
      return d.promise
    })
    .then(function (response) {
      // Do something after the first digest

      Async.call()
      .then(function (response) {
        defer.resolve(response) // The original defer
        $rootScope.$apply() // Call the second digest
      })
    })

    defer.promise.then(function(value) {
      // Do something after the second digest
      expect(value).toBe('I told you I would come!')
    })
    .finally(done)

    if($timeout.verifyNoPendingTasks())
      $timeout.flush() 
  })
})

It is like a chained async calls thing. Hope it helps the conversation. Regards

Comments

4

This answer won't add anything new to those of above, it is only intended to articulate the answer in more detailed way, as it worked for me. When I occurred the issue described in a question above, I spent much time tryng to find a way to make sure all promises had their time to finish and all assertions were asserted.

In my case I had a chain of promises, and after each of them I need to ensure the results do match my expectation. I did not create any promise using deferred, I rather invoked the existing ones.

So, the thing is that $timeout.flush() was completely enough for me. My working test looks like this:

describe("Plain command without side-effects", function() {
    it("All usecases", inject(function($timeout) {
        console.log("All together");
        expect(state.number).toEqual(1);
        cmdHistory
            .execute(increaseState, decreaseState)
            .then(function() {
                console.log("Execute works");
                expect(state.number).toEqual(2);
                return cmdHistory.redo(); // can't redo, nothing's undone
            })
            .then(function() {
                console.log("Redo would not work");
                expect(state.number).toEqual(2);
                return cmdHistory.undo();
            })
            .then(function() {
                console.log("Undo undoes");
                expect(state.number).toEqual(1);
                return cmdHistory.undo();
            })
            .then(function() {
                console.log("Next undo does nothing");
                expect(state.number).toEqual(1);
                return cmdHistory.redo(); // but still able to redo

            })
            .then(function() {
                console.log("And redo redoes neatly");
                expect(state.number).toEqual(2);
            });

        $timeout.flush();
    }));

This test is dedicated to make sure that commandHistory object works fine, it has to actions: execute and unExecute, and three methods: execute, undo, redo, all of which return promises.

Without $timeout.flush(), all I had in log output was All together, and no further log messages. Adding $timeout.flush() has fixed everything up, and now I have all messages shown and all assertions executed

UPDATE There's another option: you can write your test suite without chaining promises with then, but simply flushing after each promise has been called, so that to make sure it completes:

    it("All usecases 2", inject(function($timeout) {
        console.log("All usecases 2");
        expect(state.number).toEqual(1);

        console.log("Execute works");
        cmdHistory.execute(increaseState, decreaseState);
        $timeout.flush();
        expect(state.number).toEqual(2);

        console.log("Redo would not work");
        cmdHistory.redo(); // can't redo, nothing's undone
        $timeout.verifyNoPendingTasks();
        expect(state.number).toEqual(2);

        console.log("Undo undoes");
        cmdHistory.undo();
        $timeout.flush();
        expect(state.number).toEqual(1);

        console.log("Next undo does nothing");
        cmdHistory.undo();
        $timeout.verifyNoPendingTasks();
        expect(state.number).toEqual(1);

        console.log("And redo redoes neatly");
        cmdHistory.redo(); // but still able to redo
        $timeout.flush();
        expect(state.number).toEqual(2);
    }));

Please pay attention to the fact in some cases, when my methods like undo and redo do not return promise, I call $timeout.verifyNoPendingTasks() instead of flush. Which is hard to say if it's good or bad.

Yet in this case test looks more reasonable and much simpler.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.