3

I'm using a before block in a set of mocha unit tests and within them I'm iterating over a set of calls to get information from a REST API. I'm using chai-http to do this. However I am running into the problem that the done() method is being called before the series of n requests I make have completed. Calling done in the end block results in multiple done() calls but putting outside of the block means it's called before I'm really done! Here is an example of before block:

var flags = [];
var groups = [];

// This functions correctly 1 done() called at the end
before(function(done) {
    chai.request(server)
        .get('/groups')
        .end(function(err, res){
             groups = JSON.parse(res.text);
             done();
        });
    });

before(function(done) {
    groups.forEach(function(rec) {
        chai.request(server)
            .get('/groups/' + rec.KEYWORD_GROUP_ID + '/groupflags')
            .end(function(res, err) {
                Array.prototype.push.apply(flags, JSON.parse(res.text));
                // A done() here gets called n times
                });
        // But here it's called before the requests all end
        done();
        });

Is there a way of detecting when all of these requests have completed then I can call a single done() to ensure my tests are only executed with the correct context set up?

JSDevGuy
  • 97
  • 1
  • 8

2 Answers2

3

You could try with async.whilst(). Count up a counter to groups.length and then hit done() in the callback. Link to the function documentation: (http://caolan.github.io/async/docs.html#whilst)

Something like...

let counter = 0;
async.whilst(
    () => {
        // Test if we have processed all records
        return counter < groups.length;
    },
    (callback) => {
        let rec = groups[counter++]; // Sorry Douglas
        chai.request(server)
            .get('/groups/' + rec.KEYWORD_GROUP_ID + '/groupflags')
            .end(function (res, err) {
                Array.prototype.push.apply(flags, JSON.parse(res.text));
                callback(null, counter);
            });
    },
    (err) => {
        assert(!err, err);
        done();
    }
);
atripes
  • 1,525
  • 3
  • 18
  • 22
  • Thanks, I did end up trying a simple count but it looks like the async library is a lot more flexible and will fit the general case way better. – JSDevGuy Apr 29 '17 at 22:35
  • Sure, thanks for accepting. Can you post your solution as an edit below your original question? I'd be interested in how you solved the problem with counter and asyncronicity. – atripes May 03 '17 at 11:06
2

As Alex requested here is what I had initially as a solution:

before('delete keywords in a group', function(done) {
    var count = 0;
    var length = groups.length;

    if (length === 0) {done();}

    groups.forEach(function (rec) {
        chai.request(server)
            .delete('/keywords/' + rec.id)
            .end(function (err, res) {
                if (err) {
                    console.error('Delete keywords err: ' + err.message);
                    this.skip();
                } else {
                    count++;
                    if (count === length) {done();}
                    }
            });
        });
    });

This seems to be working but I think for any more complex cases (for example a cascade style delete) the async library provides a more elegant and reliable solution. Hence it is a better fit for the general case.

JSDevGuy
  • 97
  • 1
  • 8