Guy
Guy

Reputation: 13336

Promises: Unit testing a recursive method that fires promises in a queue one after the other

I have a method in my project that receives an array of promise returning methods. When the first one is finished it moves to the next one and so forth. I am having a hard time figuring how to unit test this method.

fireAllBatches: function (batchQueue, resolve, reject) {
    if (batchQueue.length) {
        var batch = batchQueue.pop();

        // this returns a promise
        googleCalendarService.fireBatch(batch)
            .then(function (results) {                      
                // when done fires the next one
                this.fireAllBatches(batchQueue, resolve, reject);

            }.bind(this)).catch(reject);
     } else {
        console.log('resolving firing of batches.');
        resolve();
     }
}

This is the test:

it('fireAllBatches should call fireBatch as many times as the number of the batches', function () {
    spyOn(mockGoogleCalendarService, "fireBatch").and.returnValue(q.when({}));

    datalayerObject.fireAllBatches([1, 2, 3, 4, 5, 6]);

    expect(mockGoogleCalendarService.fireBatch).toHaveBeenCalled();
    expect(mockGoogleCalendarService.fireBatch.calls.count()).toBe(6);

});

Update

After investigating and reading this answer. I was able to transform the recursive method to this:

fireAllBatches: function (batchQueue, resolve, reject) {
    var methodArray = _.map(batchQueue, function (batch) {
        return function () {
            console.log('firing batch');
            return googleCalendarService.fireBatch(batch)
        }
    });

    var resolvedPromise = $q.when(true);

    methodArray.reduce(function(cur, next) {
        return cur.then(next);
    }, resolvedPromise).then(resolve).catch(reject);

}

However, I am not sure whether it will catch errors correctly.

Upvotes: 0

Views: 662

Answers (2)

GregL
GregL

Reputation: 38161

I would mock or stub out the googleCalendarService.fireBatch() function, so you can verify what it was called with, and then you can just use a spy for the resolve and reject parameters.

Here is what I would test:

  • (Optional) Consider case where batchQueue is null/undefined.
  • It should call resolve immediately if batchQueue is empty
  • It should call the googleCalendarService.fireBatch stub once with the first batch, and then call resolve if you pass in a queue with one batch.
  • It should call the googleCalendarService.fireBatch stub twice and the resolve spy once for a queue of 2 batches.
  • Test if the googleCalendarService.fireBatch function throws an error that the reject spy gets called.

You may think of additional tests as well.

Upvotes: 1

S McCrohan
S McCrohan

Reputation: 6693

This isn't specifically an answer with regards to unit testing. However, if you're working in ES6, you could do something along these lines to avoid the recursion, and it might simplify your testing:

batchQueue.reduce( (chain,batch) => {
    return chain.then(googleCalendarService.fireBatch(batch))
}, Promise.resolve(null)).then(resolve).catch(reject);

Upvotes: 1

Related Questions