What is Orchestrate?

Orchestrate makes databases simple by powering full-text search, events, graph, and K/V storage behind a REST API.


Receive Blog Updates

Share:

Max Thayer

How to Test Code that Uses HTTP APIs using Node.js, Mocha, and Nock

06.13.2014
crash-dummies

One of the challenges of using a database like Orchestrate, is not having a local running instance of the datastore to write tests against. However, when you’re testing you do not want to test the database or the APIs that your code relies on. The goal should be to test your code, because…

  • The network is unreliable; poor connections will cause tests to fail
  • Sending data over the net is slower than sending data locally
  • Deterministic testing is awesome

In March, we showed you how to test HTTP APIs using Python and VCR, but this time I want to show off using Mocha and Nock to automate API testing in Node.js projects.

Mocha

Mocha is a testing framework for Node.js and JavaScript projects. Using it often looks like this:

describe('your project', function () {
  before(function (done) {
    doSomethingAsynchronous(done);
  });

  beforeEach(function () {
    // all your tests can now use this value
    this.name = aRandomName();
  });

  it('should do great things in life and love', function (done) {
    // the `done` callback's first argument is considered an error
    // so the test will fail if `done` receives a truthy first argument
    testSomethingAsynchronous()
    .then(function (data) {
      done();
    })
    .fail(function (err) {
      done(err);
    });
  });
});

What does testing that look like? Say you ran mocha -R nyan; this is what you’d see:

 1   -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-__,------,
 0   -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-__|  /\_/\
 0   -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_~|_( ^ .^)
     -_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ ""  ""

  1 passing (141ms)

Yep. That’s a nyan cat, passing your tests. Mocha comes with a bunch of other reporters, including several for generating code coverage reports. Check out Mocha’s docs for more.

Nock

Nock is an HTTP mocking library that can record HTTP calls and play them back, so that you can either mock an API by hand, or just record actual HTTP calls and use their responses in future test runs. Our orchestrate.js library does the former, but I’ll show you how to do the latter.

Nock works by intercepting any HTTP calls your application makes, and recording their parameters. You can then record those parameters, so future HTTP calls get the recorded responses, rather than making an HTTP request to an external service like Orchestrate. This makes your tests fast and deterministic.

To start recording, just put this in your code:

var nock = require('nock');

nock.recorder.rec({
  dont_print: true
});

Then, to work with the recorded responses, do this:

var fixtures = nock.recorder.play();

By default, Nock records JavaScript code snippets, like this:

nock('https://api.orchestrate.io:443')
  .get('/v0/addInSetTest/0')
  .reply(404, {"message":"The requested items could not be found.","details":{"items":[{"collection":"addInSetTest","key":"0"}]},"code":"items_not_found"}, { 'content-type': 'application/json',
  date: 'Fri, 13 Jun 2014 18:51:05 GMT',
  'x-orchestrate-req-id': 'ac8d94f0-f32b-11e3-82c9-12313d2f7cdc',
  'content-length': '140',
  connection: 'Close' });

Let’s see this in action.

addInSet

Consider this scenario:

  1. In a document, you have a list.
  2. You want to add an element to that list.
  3. The order of this list matters.

So you…

  1. Get the document
  2. Add your element to the list
  3. Update the document

Lots of databases have serious issues with this scenario. After you get the document but before you update it, someone else might have already updated it. Their change might invalidate yours, or your change might overwrite theirs. Either situation is problematic.

Even if it’s just you updating the document, the database might execute one of your changes before another, when you needed them to happen the other way around. This doesn’t happen in Orchestrate, but it does in many other databases.

In reality, if you need an ordered list of elements like that, you should probably use Events,
but for the sake of experimentation, we’re going to do it wrong.

We’re going to add a bunch of numbers in order, primarily because doing so will take a while. Then, we’ll record those HTTP calls, save them, and see how much faster our tests run.

I’ll be using this code. To follow along, you’ll need Node.js installed. Then, just:

git clone git@github.com:garbados/mocha_nock_demo.git
cd mocha_nock_demo
npm install
npm test
# tests take a long time
npm test
# tests complete almost instantly

How long? Here’s my console output:

addInSet
  ✓ should insert 10 numbers in order (11320ms)


1 passing (12s)

About 12 seconds. How about with recorded responses?

addInSet
  ✓ should insert 10 numbers in order (61ms)


1 passing (77ms)

77 milliseconds. Dang.

So, under the hood, what’s happening? All the magic is in test/record.js:

var nock = require('nock');
var path = require('path');
var fs = require('fs');

module.exports = function (name, options) {
  // options tell us where to store our fixtures
  options = options || {};
  var test_folder = options.test_folder || 'test';
  var fixtures_folder = options.fixtures_folder || 'fixtures';
  var fp = path.join(test_folder, fixtures_folder, name + '.js');
  // `has_fixtures` indicates whether the test has fixtures we should read,
  // or doesn't, so we should record and save them.
  // the environment variable `NOCK_RECORD` can be used to force a new recording.
  var has_fixtures = !!process.env.NOCK_RECORD;

  return {
    // starts recording, or ensure the fixtures exist
    before: function () {
      if (!has_fixtures) try {
        require('../' + fp);
        has_fixtures = true;
      } catch (e) {
        nock.recorder.rec({
          dont_print: true
        });
      } else {
        has_fixtures = false;
        nock.recorder.rec({
          dont_print: true
        });
      }
    },
    // saves our recording if fixtures didn't already exist
    after: function (done) {
      if (!has_fixtures) {
        has_fixtures = nock.recorder.play();
        var text = "var nock = require('nock');\n" + fixtures.join('\n');
        fs.writeFile(fp, text, done);
      } else {
        done();
      }
    }
  }
};

record.before checks whether you have fixtures already that should be read, or whether to record and save any HTTP calls made during tests.

record.after writes any recorded HTTP calls to disk if you don’t already have fixtures.

record.js gives us a recorder object that we use to record HTTP calls, and then save them for later use. We’ll then use it in our mocha tests like this:

var record = require('./record');

describe('your project', function () {
  var recorder = record('your_project');
  before(recorder.before);

  // ... all your tests

  after(recorder.after);
});

And voila! Our HTTP calls are saved to disk. Now the next time we run npm test, our tests will use the recordings. If you want to force a re-recording, say if your tests change, just do NOCK_RECORD=1 npm test.

You can use record.js in your own projects to your heart’s content. Happy coding!