1

Below is a simplified version of the code I am trying to test: a simple queue that is periodically emptied. For each number in the queue, an HTTP POST is made to send it to an (in this example fictional) API address. If the POST is successful, the number is shifted off the queue and the next number considered.

class Queue {
    queue: Array<number>;
    processingQueue: boolean;

    constructor(public $http: angular.IHttpService) {
        this.queue = new Array<number>();
        this.processingQueue = false;
    }

    startQueueProcessor() {
        this.processingQueue = false;

        setInterval(() => {
            if (!this.processingQueue)
                this.processQueue();
        }, 1000);
    }

    postNumber(number: number): angular.IHttpPromise<{}> {
        return this.$http.post('http://mydummyurl.com/givemeanumber', number);
    }

    processQueue(): void {
        this.processingQueue = true; // we are currently processing a queue, so make sure setInterval does not trigger another processing run

        if (this.queue.length !== 0) { // are there any numbers to deal with?
            const item = this.queue[0]; // get the first number in the queue

            this.postNumber(item) // POST it (returns a promise)
                .then(
                () => { // if successful...
                    this.queue.shift(); // we've dealt with this item, so remove it from the queue
                    this.processQueue(); // check the queue again (recurses until all items have been dealt with)
                },
                () => { // if unsuccessful...
                    this.processingQueue = false; // something went wrong, so stop
                }
                );
        } else {
            this.processingQueue = false; // queue is empty, stop processing
        }
    }

    enqueue(number: number): void { // add a number to the queue
        this.queue.push(number);
    }
}

The test I wish to create will check that, after having three items added to the queue, a single call to processQueue() will empty it.

Something like this (mocked up in Jasmine):

describe('queueing', () => {
    var queueService;

    beforeEach(inject((_$httpBackend_, $injector) => {
        httpBackend = _$httpBackend_;
        httpBackend.whenPOST().respond(200);

        queueService = $injector.get('queue');
    }));

    it('should clear queue completely when processQueue() is called once', () => {

        queueService.enqueue(1);
        queueService.enqueue(2);
        queueService.enqueue(3);

        expect(queueService.queue.length).toBe(3);

        queueService.processQueue();

        // somehow wait for processQueue() to complete

        expect(queueService.queue.length).toBe(0);
    });
});

My issue is that the second expect() always fails with the message Expected 3 to be 0. I assume this is due to the promises being returned by postNumber() not being waited for, hence the queue is not empty by the time the second expect() is called.

How do I wait for processQueue() to complete before attempting to assert that the queue has been emptied?

Outrigger
  • 57
  • 7

1 Answers1

0

You should modify processQueue to return Promise. The easiest way to do so is by marking method with async keyword and using await later on:

async processQueue(): Promise<void> 
{
    this.processingQueue = true; // we are currently processing a queue, so make sure setInterval does not trigger another processing run

    if (this.queue.length !== 0) 
    { // are there any numbers to deal with?
        const item = this.queue[0]; // get the first number in the queue

        try
        {
            await this.postNumber(item); // POST it (returns a promise)
            // if successful...
            this.queue.shift(); // we've dealt with this item, so remove it from the queue
            this.processQueue(); // check the queue again (recurses until all items have been dealt with)
        }
        catch
        {
                this.processingQueue = false; // something went wrong, so stop
        }
    } 
    else 
    {
        this.processingQueue = false; // queue is empty, stop processing
    }
}

And then in your test:

it('should clear queue completely when processQueue() is called once', async (done) => {

    queueService.enqueue(1);
    queueService.enqueue(2);
    queueService.enqueue(3);

    expect(queueService.queue.length).toBe(3);

    await queueService.processQueue();

    // somehow wait for processQueue() to complete

    expect(queueService.queue.length).toBe(0);

    done();
});    

Hope this helps.

Amid
  • 21,508
  • 5
  • 57
  • 54
  • Thanks for the suggestion @Amid. I've tried it out but I get the following error: `Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.` The first assertion goes off correctly, but then the `await` section does not seem to return anything and it times out. – Outrigger Dec 22 '16 at 17:10
  • I can see two reasons for that. Either http.post cant reach the host and the operation timed out. Or (and this is more likely) the angular.IHttpPromise is not compatiible with generic A+ promise used by typescript. See the following two links for examples of conversion: https://www.codeproject.com/articles/1019920/building-angularjs-application-with-typescript-and and http://stackoverflow.com/questions/24611078/how-convert-angular-promise-to-jquery-deferred-object – Amid Dec 23 '16 at 07:43