npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vcr-test

v1.2.0

Published

Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.

Downloads

5,673

Readme

vcr-test

Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.

Installation

npm install vcr-test --save-dev

Usage

A cassette contains all the HTTP traffic generated by your code. The first time the test runs, it should make live HTTP calls. `vcr-test`` will take care of recording the HTTP traffic and storing it. Future test runs replay the recorded traffic.

import { join } from 'node:path';
import { VCR, FileStorage } from 'vcr-test';
import { api } from './my-api'

describe('some suite', () => {
  it('some test', async () => {
    // Configure VCR
    const vcr = new VCR(new FileStorage(join(__dirname, '__cassettes__')));

    // Intercept HTTP traffic
    await vcr.useCassette('cassette_name', async () => {
      const result = await api.myAwesomeApiCall();

      // Your regular assertions
      expect(result).toBeDefined();
    });
  })
})

Terminology

  • Cassette: a file containing the recorded HTTP interactions.
  • HTTP Interaction: a HTTP Request and Response tuple

Recording Modes

VCR supports different recording modes:

  1. once: Record the HTTP interactions if the cassette has not been recorded; otherwise, playback the HTTP interactions. This is the default. Helpful when a new feature has been developed and you want to record once and playback in the future.
  2. none: Do not record any HTTP interactions; play them all back. Similar to once except it will not try to make live calls even when the cassette does not exist.
  3. update: Records new HTTP interactions, plays back the recorded ones, deletes the rest. Useful when there is a change in one of the HTTP interactions like a new field in the request or response. This mode will try to preserve the cassette as much as possible.
  4. all Record every HTTP interactions; do not play any back. Useful for one-time checks against the real endpoints.
import { VCR, RecordMode } from 'vcr-test';

const vcr = new VCR(...);
vcr.mode = RecordMode.update;

await vcr.useCassette(...);

Extensibility

Request masking

Your API calls might include sensitive data that you do not want to record in a cassette (API Keys, bearer tokens, etc). You can assign a request masker by:

import { VCR } from 'vcr-test';
const vcr = new VCR(...);
vcr.requestMasker = (req) => {
  req.headers['authorization'] = 'masked';
};

Request matching

VCR will try to find a match in a cassette that matches on url, headers, and body. However, you may want to change this behavior to ignore certain headers and perform custom body checks.

The default request matcher allows you to change some of its behavior:

import { VCR, DefaultRequestMatcher } from 'vcr-test';

const vcr = new VCR(...);

const matcher = new DefaultRequestMatcher();

// the request headers will not be compared against recorded HTTP traffic.
matcher.compareHeaders = false; 

// the request body will not be compared against recorded HTTP traffic.
matcher.compareBody = false;

// This will ignore specific headers when doing request matching
matcher.ignoreHeaders.add('timestamp');
matcher.ignoreHeaders.add('content-length');

// Assign to VCR
vcr.matcher = matcher;

Alternatively, you can extend the default request matcher:

import { DefaultRequestMatcher } from 'vcr-test';

class MyCustomRequestMatcher extends DefaultRequestMatcher {
  public bodiesEqual(recorded: HttpRequest, request: HttpRequest): boolean {
    // custom body matching logic
  }

  public headersEqual(recorded: HttpRequest, request: HttpRequest): boolean {
    // custom headers matching logic
  }

  public urlEqual(recorded: HttpRequest, request: HttpRequest): boolean {
    // custom url matching logic
  }

  public methodEqual(recorded: HttpRequest, request: HttpRequest): boolean {
    // custom method matching logic
  }
}

const vcr = new VCR(...);
vcr.matcher = new MyCustomRequestMatcher();

If you have more advanced matching needs you can implement your own Request Matcher:

/**
 * Matches an app request against a list of HTTP interactions previously recorded
 */
export interface IRequestMatcher {
  /**
   * Finds the index of the recorded HTTP interaction that matches a given request
   * @param {HttpInteraction[]} calls recorded HTTP interactions
   * @param {HttpRequest} request app request
   * @returns {number} the index of the match or -1 if not found
   */
  indexOf(calls: HttpInteraction[], request: HttpRequest): number;
}

export class MyCustomRequestMatcher implements IRequestMatcher {
  ...
}

and assign the custom implementation like this:

const vcr = new VCR(...);
vcr.matcher = new MyCustomRequestMatcher();

For more details refer to the DefaultRequestMatcher implementation.

Storage

The library comes with a File storage implementation that saves files in YAML for readibility. However, you may prefer to save the cassettes in a database and in JSON. You can change the storage and file format by creating a different storage implementation.

This is the interface you need to satisfy:

/**
 * Cassette storage
 */
export interface ICassetteStorage {
  /**
   * Loads a cassette from storage or undefined if not found.
   * @param {string} name cassette name
   * @returns {Promise<HttpInteraction[] | undefined>}
   */
  load(name: string): Promise<HttpInteraction[] | undefined>;

  /**
   * Saves HTTP traffic to a cassette with the specified name
   * @param {string} name cassette name
   * @param {HttpInteraction[]} interactions HTTP traffic
   * @returns {Promise<void>}
   */
  save(name: string, interactions: HttpInteraction[]): Promise<void>;
}

Then just initialize VCR with your implementation:

const vcr = new VCR(new DatabaseStorage());

For more details refer to the FileStorage implementation.

Request pass-through

You may want certain requests to never be recorded. You can do it this way:

import { VCR } from 'vcr-test';
const vcr = new VCR(...);
vcr.requestPassThrough = (req) => {
  return req.url.startsWith('https://example.com');
};

FAQ

How can I pretty print JSON bodies?

Here is a custom Cassette Storage implementatation that adds a new field with the formatted request and response body. It does not modify the real bodies to keep 100% fidelity with what the app sends and receives.

class PrettifiedFileStorage extends FileStorage {
  override save(name: string, interactions: HttpInteraction[]): Promise<void> {
    for (const int of interactions) {
      let contentType = int.request.headers['content-type'];
      if (contentType?.startsWith('application/json')) {
        try {
          // @ts-expect-error dynamically adding field
          int.request.bodyf = JSON.stringify(JSON.parse(int.request.body), null, 2);
        } catch (err) {
          console.error('Failed to prettify request body', err);
        }
      }

      contentType = int.response.headers['content-type'];
      if (contentType?.startsWith('application/json')) {
        try {
          // @ts-expect-error dynamically adding field
          int.response.bodyf = JSON.stringify(JSON.parse(int.response.body), null, 2);
        } catch (err) {
          console.error('Failed to prettify response body', err);
        }
      }
    }

    return super.save(name, interactions);
  }
}

const vcr = new VCR(new PrettifiedFileStorage(...));

How do I update an existing cassette because of a change?

The simplest way is to just delete the cassette and re-record it making all live calls. However, this may be tricky if the is some HTTP call is dynamic and you would not get the exact same data you were testing for. Here are some other options:

  1. Change the cassette manually, after all it is just YAML. Make sure to update the Content-Length header if the body changes!
  2. Change VCR's mode to update, run the test, then change back. This will make live calls for the requests that were not found in the cassette.