Fork me on GitHub

Node Replay

When API testing slows you down: record and replay HTTP responses like a boss

Things that will ruin your day when tests make HTTP requests to other services:

Things node-replay can do to make these problems go away:

How to use node-replay

Like this:

npm install replay

Now write some simple test case:

const assert  = require('assert');
const HTTP    = require('http');
const Replay  = require('replay');

HTTP.get({ hostname: '', path: '/api/v1/random' }, function(response) {
  var body = '';
  response.on('data', function(chunk) {
    response.body = response.body + chunk;
  response.on('end', function() {

    // Now check the request we made to the I <3 Quotes API
    assert.equal(response.statusCode, 200);
    assert.equal(response.body, 'Oxymoron 2. Exact estimate\n\n[codehappy]\n');


This, of course, will fail the first time you run it. You'll see:

Error: Connection to refused: not recording and no network access
    at Array.0 (/Users/assaf/projects/node-replay/lib/replay/
    at EventEmitter._tickCallback (node.js:192:40)

Unless you tell it otherwise, node-replay runs in replay mode. In this
mode it will replay any previously captured HTTP response, but it will not allow
any outgoing network connection.

That's the default mode for running tests. "Why?" you ask. Good question.
Running in replay mode forces any test you run to use recorded reponses, and
so it will run (and fail or pass) the same way for anyone else, any other day of
the week, on whatever hardware they use. Even if they're on the AT&T network.

Running in replay mode helps you write repeatable tests. Repeatable tests are
a Good Thing.

So the first thing you want to do to get that test to pass, is to run
node-replay in record mode. In this mode it will replay any recorded
response, but if no response was recorded, it will make a request to the server
and capture the response.

Let's do that:

REPLAY=record node test.js

That wasn't too hard, but the test is still failing. "How?" You must be
wondering and scratching your head in total disbelief. It's actually quite

Every request you make to 'I 3> Quotes' returns a different quote, and that test
is looking for a very specific quote. So the test will fail, and each time fail
with a different error.

So one way we can fix this test is to change the assertion. Look at the error
message, get the actual quote and make the assertion look for that value.

Now run the test:

$ node test.js
=> Woot!

Did the test pass? Of course it did. Run it again. Still passing? Why, yes.

So let's have a look at that captured response. All the respones recorded for
'I ' will be listed here:

ls fixtures/

There should be only one file there, since we only recorded one response. The
file name is a timestamp, but feel free to rename it to something more

The name of a response file doesn't matter, it can be whatever you want. The
name of the directory does, though, it matches the service hostname (and port
when not 80).

So that was one way to fix the failing test. Another one is to change the
recorded response to match the assertion. Being able to edit (and create new)
responses is quite important. Sometimes it's the easiest way to create mock
responses for testing, e.g. if you're trying to test failure conditions that are
hard to come by.

So let's edit the response file and change the body part, so the entire response
reads like this:


HTTP/1.1 200 OK
server: nginx/0.7.67
date: Fri, 02 Dec 2011 02:58:03 GMT
content-type: text/plain
connection: keep-alive
etag: "a7131ebc1e81e43ea9ecf36fa2fdf610"
x-ua-compatible: IE=Edge,chrome=1
x-runtime: 0.158080
cache-control: max-age=0, private, must-revalidate
content-length: 234
x-varnish: 2274830138
age: 0
via: 1.1 varnish

Oxymoron 2. Exact estimate


All responses are stored as text files using the simplest format ever, so you
can edit them in Vim, or any of the many non-Vim text editors in existence:

If you need to use regular expressions to match the request URL, add REGEXP
between the method and path, for example:

GET REGEXP /\/Aregexp\d/i

HTTP/1.1 200 OK
Content-Type: text/html


We've got them. Just enough to make you happy and not enough to take all day to

The first and most obvious is the mode you run node-reply in:

bloody -- All requests go out, none get replayed. Use this if you want to
remember what life was before you started using node-replay. Also, to test
your code against changes to 3rd party API, because these do happen. Too often.

cheat -- Replays recorded responses, and allow HTTP outbound requests. This
is mighty convenient when you're writing new tests or changing code to make new,
un-recorded HTTP requests, but you haven't quite settled on which requets to
make, so you don't want any responses recorded quite yet.

record -- Replays recorded responses, or captures responses for future
replay. Use this whenever you're writing new tests or code that makes new HTTP

replay -- Replays recorded responses, does not allow outbound requests.
This is the default mode. That's another way of saying, "you'll be running in
this mode most of the time".

You can set the mode by setting the environment variable REPLAY to one of
these values:

REPLAY=record node test.js

Of from your code by setting replay.mode:

const Replay = require('replay');
Replay.mode = 'record';

Of course, node-replay needs to store all those captured responses somewhere,
and by default it will put them in the directory fixtures. Bet you have an
idea for a better directory name. Easy to change.

Like this:

Replay.fixtures = __dirname + '/fixtures/replay';

You can tell node-replay what hosts to treat as "localhost". Requests to
these hosts will be routed to, without capturing or replay. This is
particularly useful if you're making request to a test server and want to use
the same URL as production.

For example:


If you don't want requests going to specific server, you can add them to the
drop list. For example, Google Analytics, where you don't care that the request
go through, and you don't want to record it.

Replay.drop('', '');

Likewise, you can tell node-reply to pass through requests to specific hosts:


If you're running into trouble, try turning debugging mode on. It helps.

$ DEBUG=replay node test.js
=> Requesting
=> Woot!

By default, node-replay will record the following headers with each request,
and use only these headers when matching pre-recorded requests:

You can modify the list of matched headers, adding or removing headers, by
changing the value of Replay.headers. The value is an array of regular

For example, to capture content-length (useful with file uploads):


Since headers are case insensitive, we always match on the lower case name.


To make all that magic possible, node-replay replaces
require('http').request with its own method. That method returns a
ProxyRequest object that captures the request URL, headers and body.

When it's time to fire the request, it gets sent through a chain of proxies.
The first proxy to have a response, returns it (via callback, this is Node.js
after all). That terminates the chain. A proxy that doesn't have a response
still has to call the callback, but with no arguments. The request will then
pass to the next proxy down the chain.

The proxy chain looks something like this:

Loading pre-recorded responses to memory, from where they can be replayed, and
storing new ones on disk, is handled by ... cue big band ... the Catalog.

Final words

node-replay is released under the MIT license. Pull requests are welcome.