/tape-nock

:vhs: Automatically record and playback HTTP calls for each tape test.

Primary LanguageJavaScriptISC LicenseISC

tape-nock

npm travis standard Package Quality Coverage Status - Master

Automatically record and playback HTTP calls for each tape test. This package is really just a decorator that wraps each test individual test with nock's Nock Back feature. This helps avoid the all nockBack wrapping code which can make tests less clear.

Install

npm install tape-nock --save-dev

Usage

var path = require('path')
var tape = require('tape') // still need to require tape
var tapeNock = require('tape-nock')

// call tapeNock with tape and an options object
var test = tapeNock(tape, { //options object to be passed to nock, not required
  fixtures: path.join(__dirname, 'fixtures'), // this is the default path
  mode: 'dryrun' // this is the default mode
  defaultTestOptions: { // optionally provide default options to nockBack for each test
    before: function () {
      console.log('a preprocessing function, gets called before nock.define')
    },
    after: function () {
      console.log('a postprocessing function, gets called after nock.define')
    }
  }
})

(see nockBack test options for more information on before, after, and afterRecord functions used in defaultTestOptions)

Now just write your tape tests as usual:

var request = require('request')

test('do it live on the internet', function(t) {
  request.get('https://registry.npmjs.org', function (err, res) {
    t.error(err)
    t.ok(res)
    t.end()
})

in dryrun mode, the above test will go to the internet and nothing will be recorded.

Once your tests are perfected, Create fixture files by using the record mode. This captures all HTTP calls per test and saves it to the fixtures directory.

To record, set the NOCK_BACK_MODE environment variable:

NOCK_BACK_MODE=record npm test

...or do it programatically via the tape-nock options object:

var test = tapeNock(tape, {
  fixtures: path.join(__dirname, 'fixtures'),
  mode: 'record' // record mode!
})

A new file called do it live on the internet.json will be created in the fixtures directory. It will contain an array of HTTP calls that were made during the test. Each test will have its own JSON.

Once a fixture exists for a test, it will be used every time in dryrun mode. To re-record, you'll need to delete the JSON file.

To make this easier, add scripts to your package.json for easy recording/running:

{
  "scripts" {
    "test": "tape test/*js"
    "test:record": "NOCK_BACK_MODE=record npm test",
    "test:wild": "NOCK_BACK_MODE=wild npm test",
    "test:lockdown": "NOCK_BACK_MODE=lockdown npm test",
    "test:overwrite": "rm test/fixtures/*.json & npm run test:record"
  }
}

Can still mock things

It is also possible to manually mock at the same time so a request NEVER hits a URL.

Just get a copy of nock from tape-nock via .nock and use it:

var tape = require('tape')
var tapeNock = require('tape-nock')
var test = tapeNock(tape, {
  fixtures: path.join(__dirname, 'fixtures') //defaults to this path
})

var request = require('request')
test('able to get a copy of nock from test.nock and use it', function (t) {
  // get a copy of nock
  var nock = test.nock

  // use it to mock a URL. This mock will live even if NOCK_BACK_MODE=wild
  nock('http://registry.npmjs.org').get('/clockmoji').reply(200, {'yep': 'it works'})

  request.get('http://registry.npmjs.org/clockmoji', process)

  function process (err, resp) {
    t.error(err, 'no error')
    t.equals(JSON.parse(resp.body).yep, 'it works', 'able to mock directly with nock instance')
    t.end()
  }
})

NOCK_BACK_MODE

Use the NOCK_BACK_MODE environment variable (details) to control the mode of nockBack.

Here is a recap

  • wild: all requests go out to the internet, don't replay anything, doesn't record anything
  • dryrun: The default, use recorded nocks, allow http calls, doesn't record anything, useful for writing new tests
  • record: use recorded nocks, record new nocks
  • lockdown: use recorded nocks, disables all http calls even when not nocked, doesn't record

Nock Back options

Its also possible to pass nockBack options through tape's options object.

This is helpful for doing filteringPath or filteringRequestBody (check out the PROTIP... that can be done in an "after" function).

For Example, this test will use the after function passed in, which will make all "time-based-param" params in the path and replace them with 123. This will ensure time-based parameters will still be mock-able (the path in the fixture JSON will need to be manually updated to match).

var after = function (scope) {
  scope.filteringPath(/time-based-param=[^&]*/g, 'time-based-param=123')
}

test('pass through opts to nockback', {after: after}, function (t) {
  request.get('http://registry.npmjs.com?time-based-param=1455231758348', function (err, resp) {
    t.error(err)
    t.equals(JSON.parse(resp.body).haha, 'no secrets for you', 'secrets are protected')
    t.end()
  })
})

Using supertest with tape-nock

Since nockBack will mock all HTTP requests, using supertest can be tricky. Here is an example of how to avoid mocking/recording local connections when using supertest.

Here is our application. It simply hits http://httpbin.org/get which echos info back. We want to have nockBack record/mock the httpbin request but still allow supertest http requests to 127.0.0.1 to pass through for all tests. This is done by leveraging the defaultTestOptions.

app.js

const express = require('express')
const request = require('superagent')

var app = express()

app.get('/myapp/version', function (req, res) {
  superagent
    .get('http://httpbin.org/get')
    .end(function (err, response) {
      res.status(200).json({
        version: '0.1.0',
        url: response.body.url
      })
    })
})

module.exports = app

test.js

const supertest = require('supertest');
const app = require('../app.js');

const tape = require('tape');
const tapeNock = require('tape-nock');
const nock = tapeNock.nock;

const opts = {
  // after recording the fixtures, remove any scopes that hit 127.0.0.1
  // this is not necessary with our before function below, but it makes it a bit cleaner.
  afterRecord: function (scopes) {
    var localhost = /http:\/\/127\.0\.0\.1.*/;
    scopes = scopes.filter(function (s) {
      return !localhost.test(s.scope);
    });

    return scopes;
  },
  before: function () {
    // allow connections to 127.0.0.1 even when NOCK_BACK_MODE=lockdown
    nock.enableNetConnect('127.0.0.1');
  }
};

// call tapeNock with tape and an options object
const test = tapeNock(tape, { defaultTestOptions: opts });

// note that we're passing in test.options here
// which has our special "afterRecord" and "before" functions
test('hit version url', function (t) {
  supertest(app)
    .get('/myapp/version')
    .expect(200, {
      url: 'http://httpbin.org/get',
      version: '0.1.0'
    })
    .end(function (err, res) {
      t.error(err, 'no error');
      t.equals(res.body.url, 'http://httpbin.org/get', 'url is correct');
      t.end();
    });
});

Contributing

Contributions welcome! Please read the contributing guidelines first.

License

ISC