简体   繁体   中英

Test async.waterfall in node.js with sinon.js timers

I have the following function that I wish to be tested which use async.js heavily in it:

MyClass.prototype.pipeline = function(arg1, arg2) {
    ...
    async.waterfall([
        async.apply(self.a.f.bind(self.a), arg1, arg2),
        function(data, callback) {
            async.each(data, function(d, callback) {
                async.waterfall([
                    async.apply(self.b.f.bind(self.b), d),
                    self.c.f.bind(self.c),
                    self.d.f.bind(self.d),
                    self.e.f.bind(self.f)
                ], function(err, results) {
                    if (err) {
                        ...
                    }
                    callback();
                });
            }, function(err) {
                callback(err, data);
            });
        }
    ], function (err, result) {
        ...
    });
};

Now I know I could separate a-lot of what's going on in here to separate functions, but it's a pipeline of streamlined action passing data from one another after the previous has finished, so I wish to keep it like this instead for example separate the function function(data, callback) {...} with a name like BCDEpipeline. Anyway, my problem is that I do some assertions based on the done callback of the first async.waterfall() done function, problem is it gets called later (deferred) even tho I stubbed a, b, c, d, and e functions already and made them yield the next callback immediately. Note that I can't just stub async.waterfall() and make it yield its done callback because I'll be left with crucial branches of the code untested (The inner done callbacks of the async.each() and the second async.waterfall() . I've tried to use sinon fake timers and used this.clock.tick(0); after invoking the MyClass.prototype.pipeline() function like so:

var obj = new MyClass();
obj.pipeline(5, 3);
this.clock.tick(0);
/* assertions */
...

But even so the assertions parts are being executed before any done function is being called. I tried to dig into the async library code to see how it calls its done functions be it's too much of a headache and I couldn't figure out why even tho the done callbacks calls are deferred and with the sinon fake timers, my assertion code is still executing first. If I use some nested setImmediate() calls it works fine, but that's the worst solution for this problem.

You left out a lot of stuff so it is hard to tell what this is supposed to do. Mostly to be honest this looks like noise to me although thats not your fault.

If you don't actually need to do lots of IO in parallel then here is what I suggest. Switch to ES2017 and babel. Use classes with @autobind or syntax like:

class Test {
  constructor(testB) {
    this.testB = testB;
  }

  fuzz = async x => x+1;

  async foo = (bar) => {
    try {
      const result = await this.testB.blah(bar);
      return await this.fuzz(result);
    } catch (e) {
      console.error(e);
      return null;
    }
  }

  async fizz = (bars) => await Promise.all(bars.map(this.foo));
}

Then use ava for testing. This way you have a clean way ( await ) to handle basic flow for async functions with a loop and async testing, and your methods' this will always point to your instances.

Okay, it was a bug in older version according to the following issues I guess: https://github.com/caolan/async/issues/609
https://github.com/caolan/async/issues/106

Although they're pretty old and I've used newer version, I've just updated my dependencies and it's now working fine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM