ericclemmons / node-recorder

Simple recording & replaying of HTTP requests for predictable development & testing.
MIT License
97 stars 5 forks source link

node-recorder logo


node-recorder demo


Installation

$ yarn add node-recorder --dev
# or
$ npm install node-recorder --save-dev

Getting Started

Recorder Modes

Using node --require

$ node -r node-recorder path/to/server.js

(This also works with mocha!)

Setting the mode via RECORDER=...

$ RECORDER=ignore node -r node-recorder path/to/server.js

Using Jest

Included is a jest-preset that will automatically include node-recorder and a custom plugin to make toggling modes easier.

// jest.config.js
module.exports = {
  preset: "node-recorder/jest-preset"
};

Now, running jest --watch will add a new r option:

Watch Usage
 › Press a to run all tests.
 › Press f to run only failed tests.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press r to change recording mode from "REPLAY".
 › Press Enter to trigger a test run.

Pressing r will toggle between the various modes:

  ╭─────────────────────────────╮
  │                             │
  │   node-recorder:  RECORD    │
  │                             │
  ╰─────────────────────────────╯

Configuring recorder.config.js

Within your project, you can create a recorder.config.js that exports:

// recorder.conig.js
module.exports = {
  identify(request, response) {...},
  ignore(request) {...},
  normalize(request, response) {...}
}

identify a request or `response

This is useful when network requests are stateful, in that they rely on an authorization call first, then they pass along a token/cookie to subsequent calls:

  1. Suppose you login by calling /login?user=foo&password=bar.
  2. The response contains { "token": "abc123" }3. Now, to get data, you call/api?token=abc123`.

When recording recordings, the token abc123 isn't clearly associated with the user foo.

To address this, you can identify the request and response, so that the recordings are aliased accordingly:

identify(request, response) {
  const { user, token } = request.query

  if (request.href.endsWith("/login")) {
    // We know the user, but not the token yet
    if (!response) {
      return user
    }

    // Upon login, associate this `user` with the `token`
    return [user, response.body.token]
  }

  // API calls supply a `token`, which has been associated with a `user`
  if (request.href.endsWith("/api")) {
    return token
  }
}

Now, when recorded recordings will look like:

This way, similar-looking network requests (e.g. login & GraphQL) can be differentiated and easily searched for.

ignore a request

Typically, you don't want to record recordings for things like analytics or reporting.

// recorder.conig.js
module.exports = {
  ignore(request) {
    if (request.href.includes("www.google-analytics.com")) {
      return true;
    }

    return false;
  }
};

normalize a request or response

Recordings are meant to make development & testing easier, so modification is necessary.

module.exports = {
  normalize(request, response) {
    // Suppose you never care about `user-agent`
    delete request.headers["user-agent"];

    // We may not have a response (yet)
    if (response) {
      // ...or the `date`
      delete response;
    }
  }
};

MIT License

Author