简体   繁体   中英

Nodejs - Mocha, Chai multiple async testing

Complete NodeJS testing noob here. Trying to individually test functions that are called through my API (meaning, rather than make an http request to a specific endpoint, which usually invokes several functions, which in turn make requests to different third party APIs, I want to test the functions themselves separately). The way they're called is I've built a class for each data source (data source = third party API), each class contains the same functions with the same exact signatures - getData and convertData , and return a callback with the results.

I've also created a module that creates many user mocks, since each user context returns different data (meaning, a user object is fed into getData , which uses certain user properties in order to determine what data should be returned).

The way I wanted to test this was to create numerous mocks, then run the functions for each. This is what I've got so far:

// Data sources to iterate over. Each is a class instance acquired through "require".
var dataSources = [
    source1,
    source2,
    source3,
    source4
];

describe('getData', function() {       
    this.timeout(10000);
    describe('per data source,', function() {
        context('standard call', function() {

            // Associative array to hold the data returned, a key for each data source.
            var finalResults = {};

            // Iterate over all data sources
            _.forEach(dataSources, function(dataSource) {

                // Generate user mocks
                var users = userMocks(10);

                // Iterate over all users. 
                _.forEach(users, function (user) {

                    // Call each data source with each of the users.
                    // Numbers of calls to make - (users * data-sources), so in this case - 10*4.
                    dataSource.getData(user, function (err, data) {
                        if (err) return done(err);

                        // Convert the data returned to my format
                        dataSource.convertData(data, function (err, processedData) {
                            if (err) return done(err);

                            // Populate finalResults with converted data from each source
                            if (finalResults[dataSource.sourceName]) {
                                finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData);
                            } else {
                                finalResults[dataSource.sourceName] = processedData;
                            }
                        });
                    });
                });
            });

            it('should return something', function(done) {
                _.forEach(finalResults.keys, function(key) {
                    expect(finalResults[key]).to.not.be.empty;
                    expect(finalResults[key].length).to.be.greaterThan(0);
                });
                setTimeout(function() {
                    done();
                }, 10000);
            })
        });
     });
});

});`

This works (or at least the test passes when the query is valid, which is what I wanted), but it's cumbersome and (so very) far from elegant or effective, specifically the usage of timeout rather than using promises, async of some sort, or maybe a different alternative I'm not yet familiar with.

Since most of the resources I found ( http://alanhollis.com/node-js-testing-a-node-js-api-with-mocha-async-and-should/ , https://developmentnow.com/2015/02/05/make-your-node-js-api-bulletproof-how-to-test-with-mocha-chai-and-supertest/ , https://justinbellamy.com/testing-async-code-with-mocha/ , just to name a few) discuss direct API testing rather than specific async functions, I would love to get some input/best practices tips from more experienced Noders.

You need to know when bunch of asynchronous operations complete. Elegant way to test that is to use promises and promise aggregation:

Promise.all([ promise1, promise2, promise3 ]).then(function(results) {
  // all my promises are fulfilled here, and results is an array of results
});

Wrap your dataSources into a promises using bluebird . You don't need to modify tested code self, bluebird provides convenience method:

var Promise = require('bluebird')
var dataSources = [
    source1,
    source2,
    source3,
    source4
].map(Promise.promisifyAll);

Use newly promisified functions to create promise for each call:

   context('standard call', function() {
        var finalResults = {};
        var promiseOfResults = datasources.map(function(dataSource) {
            var users = userMocks(10);
            // Promise.all will take an array of promises and return a promise that is fulfilled then all of promises are
            return Promise.all( users.map(function(user) {
                // *Async functions are generated by bluebird, via Promise.promisifyAll
                return dataSource.getDataAsync(user)
                     .then(dataSource.convertDataAsync)
                     .then(function(processedData) {
                        if (finalResults[dataSource.sourceName]) {
                            finalResults[dataSource.sourceName] = finalResults[dataSource.sourceName].concat(processedData);
                        } else {
                            finalResults[dataSource.sourceName] = processedData;
                        }
                    });
            });
        });
        // promiseOfResults consists now of array of agregated promises
        it('should return something', function(done) {
            // Promise.all agregates all od your 'datasource' promises and is fulfilled when all of them are
            // You don't need the promise result here, since you agegated finalResults yourself
            return Promise.all( promiseOfResults ).then(function() {
                _.forEach(finalResults.keys, function(key) {
                    expect(finalResults[key]).to.not.be.empty;
                    expect(finalResults[key].length).to.be.greaterThan(0);
                });
                done();
            });
        });

Rest of your test should use same Promise.all( promiseOfResults ) , unless you need new set of results.

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