33

I want to test a asynchronous javascript function that runs in node.js and makes a simple request to a http api:

const HOST = 'localhost';
const PORT = 80;

http = require('http');

var options = {
    host: HOST,
    port: PORT,
    path: '/api/getUser/?userCookieId=26cf7a34c0b91335fbb701f35d118c4c32566bce',
    method: 'GET'
};
doRequest(options, myCallback);

function doRequest(options, callback) {

    var protocol = options.port == 443 ? https : http;
    var req = protocol.request(options, function(res) {

        var output = '';
        res.setEncoding('utf8');

        res.on('data', function(chunk) {
            console.log(chunk);
            output += chunk;
        });

        res.on('error', function(err) {
            throw err;
        });

        res.on('end', function() {
            var dataRes = JSON.parse(output);
            if(res.statusCode != 200) {
                throw new Error('error: ' + res.statusCode);
            } else {
                try {
                    callback(dataRes);                        
                } catch(err) {
                    throw err;
                }
            }
        });

    });

    req.on('error', function(err) {
        throw err;
    });

    req.end();

}

function myCallback(dataRes) {
    console.log(dataRes);
}

Executed this code works and the response will be displayed as expected.

If I execute this in a mocha test the request is not executed:

describe('api', function() {
    it('should load a user', function() {
        assert.doesNotThrow(function() {
            doRequest(options, myCallback, function(err) {
                if (err) throw err;
                done();
            });
        });
        assert.equal(res, '{Object ... }');
    });
});

The Problem is, that no code after:

var req = protocol.request(options, function(res) {

is executed not even a simple console.log.

Can anybody help?

3 Answers 3

50

You have to specify the callback done as the argument to the function which is provided to mocha - in this case the it() function. Like so:

describe('api', function() {
    it('should load a user', function(done) { // added "done" as parameter
        assert.doesNotThrow(function() {
            doRequest(options, function(res) {
                assert.equal(res, '{Object ... }'); // will not fail assert.doesNotThrow
                done(); // call "done()" the parameter
            }, function(err) {
                if (err) throw err; // will fail the assert.doesNotThrow
                done(); // call "done()" the parameter
            });
        });
    });
});

Also, the signature of doRequest(options, callback) specifies two arguments though when you call it in the test you provide three.

Mocha probably couldn't find the method doRequest(arg1,arg2,arg3).

Did it not provide some error output? Maybe you can change the mocha options to get more information.

EDIT :

andho is right, the second assert would be called in parallel to assert.doesNotThrow while it should only be called in the success callback.

I have fixed the example code.

EDIT 2:

Or, to simplify the error handling (see Dan M.'s comment):

describe('api', function() {
    it('should load a user', function(done) { // added "done" as parameter
        assert.doesNotThrow(function() {
            doRequest(options, function(res) {
                assert.equal(res, '{Object ... }'); // will not fail assert.doesNotThrow
                done(); // call "done()" the parameter
            }, done);
        });
    });
});
Sign up to request clarification or add additional context in comments.

3 Comments

in your example, wouldn't assert.equal(res, '{Object ... }'); be called before done is called?
There's no reason to write if (err) throw err, you can just call done(err). See mocha ref
@risadinha Your first sentence "You have to specify the callback done as the argument to the function which is provided to mocha - in this case the it() function." is very important. Thanks
4

If you have an asynchronous function that does not support callbacks, or if you think using unnecessary callbacks is... unnecessary, then you can also just turn the test into an async test.

instead of:

it('should be able to do something', function () {});

simply do:

it('should be able to do something', async function () {});
                                     ^^^^^

Now you can await async functions:

it('should be able to do something', async function () {
  this.timeout(40000);

  var result = await someComplexFunction();

  assert.isBelow(result, 3);
});

1 Comment

Can you please point me to documentation of references regarding this? I'm looking for the why :)
-1

I've done a very similar test in my project for an http client. I paste the code here and hope is useful. Here is the client (my nodejs server use express and I use promise for error handling):

var http = require('http');
var querystring = require('querystring');

module.exports = {
  get: function(action, params, res, callback) {
    doPromiseRequest(action, querystring.stringify(params), callback, 'GET', 'application/json')
      .then((response) => callback(response))
      .catch((error) => {
        res.status(500);
        res.render('error', {layout: false, message: error.message, code: 500});
      });
  },
}

function doPromiseRequest(action, params, callback, method, contentType) {
    var options = {
      hostname: 'localhost',
      port: 3000,
      path: '/api/v1/' + action.toString(),
      method: method,
      headers: {
        'Content-Type': contentType,
        'Content-Length': Buffer.byteLength(params)
      }
    };

    return new Promise( (resolve, reject) => {

      var req = http.request(options, 
        function(response) {
          response.setEncoding('utf8');

          var data = '';
          response.on('data', function(chunk) {
            data += chunk;
          });

          response.on('end', function() {
            var parsedResponse;

            try {
              parsedResponse = JSON.parse(data);
            } catch(err) {
              reject({message: `Invalid response from hurricane for ${action}`});
              return;
            }

            if (parsedResponse.error)
              reject(parsedResponse.error);
            else
              resolve(parsedResponse);
          });

          response.on('error', function(err){
            console.log(err.message);
            reject(err);
          });
        });

      req.on('error', function(err) {
        console.log(err);
        reject({message: err.message});
      });

      req.write(params);
      req.end(); 
    });    
}

And here is the test:

var http = require('http');
var expect = require('chai').expect;
var sinon = require('sinon');
var PassThrough = require('stream').PassThrough;

describe('Hurricane Client tests', function() {
  before(function() {
    this.request = sinon.stub(http, 'request');
  });

  after(function() {
    http.request.restore();
  });

  it('should convert get result to object', function(done) {
    var expected = { hello: 'world' };
    var response = new PassThrough();
    response.statusCode = 200;
    response.headers = {}
    response.write(JSON.stringify(expected));
    response.end();

    var request = new PassThrough();

    this.request.callsArgWith(1, response).returns(request);

    client.get('any', {}, null, function(result) {
      expect(result).to.eql(expected);
      done();
    });
  });
});

2 Comments

correct me if I'm wrong but the test won't actually work as expected, since if the expect assertion fails, the error will get thrown outside the scope of the test (as the assertion is in a callback). see here for how to fix
or to be precise, the test will fail due to done not being called, but the message won't really include the reason from the asserts

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.