Testing asynchronous mongodb calls in nodejs with jasmine-node
Asked Answered
S

2

8

I am using jasmine-node to run tests against my nodejs functions. Being new to nodejs and mongodb, the first thing i ran into was testing some database calls, and I immediately got stuck, due to the asynchronous nature of nodejs.

What I want to do is:

1) Add an add function to add new entries to a mongodb table

2) Receive a status string from that function to verify the action's status

The following is the code of my spec. In the beforeEach call I initialise the database. As you can see in the implementation, it is only instantiated once, because of a condition asking if it already exists.

var mongo = require('../mongo.js');

describe('mongo', function() {

    // generate a random number in order to test if the written item and the retrieved result match
    var randomNumber = Math.random();

    var item = {
        'cities': {
            'london': randomNumber
        }
    };

    beforeEach(function() {

        mongo.init();

        waitsFor(function() {
            return mongo.getCollection();
        }, "should init the database", 10000);

    });

    it('should return "added" after adding an item to the database', function() {

        var result;

        waitsFor(function() {
            result = mongo.add(item);

            // the result value here is always undefined, 
            // due to the problem i'm having in my implementation
            return result !== undefined;

        }, "adding an item to the database", 10000);

        runs(function() {
            expect(result).toEqual('added');
        });

    }); 

});

Now, for every database query, I can define a callback function which is executed when the query has been run successfully. What I don't know how to achieve is delivering the result from the mongodb callback back the the spec.

This is the current implementation of the database functions:

var mongo  = require('mongodb'),
    Server = mongo.Server,
    Db     = mongo.Db;

var server = new Server('localhost', 27017, {auto_reconnect: true});
var db     = new Db('exampleDb', server);

var collection = false;

// initialize database
var init = function() {
    if (collection === false) {
        db.open(dbOpenHandler);
    }
};

var dbOpenHandler = function(err, db) {
    db.collection('myCollection', dbCollectionHandler);
};

var dbCollectionHandler = function(err, coll) {
    collection = coll;
};

/** returns the current db collection's status
  * @return object db collection
  */
var getCollection = function() {
    return collection !== false;
};

/** Add a new item to the database
  * @param object item to be added
  * @return string status code
  */
var add = function(item) {

    var result = collection.insert( item, {safe: true}, function(err) {

        // !! PROBLEM !!
        // this return call returns the string back to the callee
        // question: how would I return this as the add function's return value
        return 'added';

    });

};

// module's export functions
exports.init = init;
exports.getCollection = getCollection;
exports.add = add;

I'm also open for other approaches on how to test database calls in mongodb. I've read a bunch of articles about this topic, but none of them covers my particular case.

SOLUTION

Finally, and with the help of JohnnyHK's answer, I managed to make it work with a callback. Look at the following test case to understand what I did:

it('should create a new item', function() {

    var response;

    mongo.add(item, function( err, result) {
        // set result to a local variable
        response = result;
    });

    // wait for async call to be finished
    waitsFor(function() {
        return response !== undefined;
    }, 'should return a status that is not undefined', 1000);

    // run the assertion after response has been set
    runs(function() {
        expect(response).toEqual('added');
    });

)}
Snuffer answered 23/9, 2012 at 10:41 Comment(4)
Take a look at this blog post on the topic: thelambdacalculus.wordpress.com/2011/02/28/5Fifth
Thanks for the link, actually I had already figured out the asynchronous part, but it is good to know that I'm not the only one to do it this way. What I still don't get is how to return a value from the called function.Snuffer
You can't return a value from an asynchronous function that depends upon the asynchronous result; you have to use a callback to deliver asynchronous results to the caller.Fifth
Can you or somebody else give a simple example how to accomplish this?Snuffer
F
3

You would have to change your add method to accept a callback parameter so that it can deliver the asynchronous result to the caller via that callback:

var add = function(item, callback) {
    collection.insert(item, {safe: true}, function(err) {
        callback(err, 'added');
    });    
};
Fifth answered 24/9, 2012 at 18:25 Comment(1)
thanks, that worked for me. I will post my specific solution below.Snuffer
T
13

You can do this much more cleanly now with the done function in jasmine-node:

it('should create a new item', function(done) {
    mongo.add(item, function(error, result) {
        expect(result).toEqual('added');
        done();
    });
});

This test will wait until done() is called asynchronously. There's a default timeout of 5 seconds, after which your test will fail. You can change this to, say, 15 seconds like so:

it('should create a new item', function(done) {
    mongo.add(item, function(error, result) {
        expect(result).toEqual('added');
        done();
    });
}, 15000);
Tetragon answered 23/5, 2013 at 16:59 Comment(1)
Great answer! Thanks pal, you made my day. I spent a whole morning trying to figure out how to test an async function in mocha. It turns out, done function works in mocha as well.Craw
F
3

You would have to change your add method to accept a callback parameter so that it can deliver the asynchronous result to the caller via that callback:

var add = function(item, callback) {
    collection.insert(item, {safe: true}, function(err) {
        callback(err, 'added');
    });    
};
Fifth answered 24/9, 2012 at 18:25 Comment(1)
thanks, that worked for me. I will post my specific solution below.Snuffer

© 2022 - 2024 — McMap. All rights reserved.