CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nock

HTTP server mocking and expectations library for Node.js testing environments

67

0.98x
Overview
Eval results
Files

fixture-testing.mddocs/

Fixture-Based Testing (Back Mode)

This document covers nock's "Back" mode, an advanced fixture-based testing system that automatically manages HTTP fixtures with different operational modes for various testing scenarios.

Back Interface

The Back system provides a workflow for fixture-based testing with automatic fixture management.

interface Back {
  (fixtureName: string, nockedFn: (nockDone: () => void) => void): void;
  (
    fixtureName: string,
    options: BackOptions,
    nockedFn: (nockDone: () => void) => void
  ): void;
  (fixtureName: string, options?: BackOptions): Promise<{
    nockDone: () => void;
    context: BackContext;
  }>;
  
  currentMode: BackMode;
  fixtures: string;
  setMode(mode: BackMode): void;
}

const back: Back;

Back Modes

Back operates in different modes that control how fixtures are handled:

type BackMode = 'wild' | 'dryrun' | 'record' | 'update' | 'lockdown';

Mode Descriptions

  • 'wild': Allow all real HTTP requests, no fixture loading or recording
  • 'dryrun': Default mode, load fixtures if they exist, allow real requests if no fixture
  • 'record': Record real HTTP requests and save them as fixtures
  • 'update': Update existing fixtures with new recordings
  • 'lockdown': Only use existing fixtures, throw error if fixture missing

Setting Modes

const nock = require("nock");

// Set mode globally
nock.back.setMode("record");

// Check current mode
console.log(nock.back.currentMode); // 'record'

// Can also be set via environment variable
process.env.NOCK_BACK_MODE = "lockdown";

Fixture Directory

Configure where fixtures are stored:

// Set fixture directory
nock.back.fixtures = "./test/fixtures";

// All fixture files will be created/loaded from this directory

Basic Usage Patterns

Callback Style

back(fixtureName: string, nockedFn: (nockDone: () => void) => void): void;
const nock = require("nock");

nock.back.fixtures = "./test/fixtures";
nock.back.setMode("dryrun");

describe("API Tests", () => {
  it("should get user data", (done) => {
    nock.back("get-user.json", (nockDone) => {
      // Your test code here
      fetch("https://api.example.com/users/1")
        .then(response => response.json())
        .then(user => {
          expect(user.id).toBe(1);
          nockDone(); // Mark nock interactions as complete
          done();
        });
    });
  });
});

Promise Style

back(fixtureName: string, options?: BackOptions): Promise<{
  nockDone: () => void;
  context: BackContext;
}>;
describe("API Tests", () => {
  it("should get user data", async () => {
    const { nockDone, context } = await nock.back("get-user.json");
    
    try {
      const response = await fetch("https://api.example.com/users/1");
      const user = await response.json();
      
      expect(user.id).toBe(1);
      
      // Verify all fixtures were used
      context.assertScopesFinished();
    } finally {
      nockDone();
    }
  });
});

Back Options

interface BackOptions {
  before?: (def: Definition) => void;
  after?: (scope: Scope) => void;
  afterRecord?: (defs: Definition[]) => Definition[] | string;
  recorder?: RecorderOptions;
}

Before Hook

Process fixture definitions before they're loaded:

nock.back("api-test.json", {
  before: (def) => {
    // Modify fixture definition before loading
    if (def.path === "/users") {
      def.response = { users: [], modified: true };
    }
    
    // Remove sensitive headers
    if (def.reqheaders) {
      delete def.reqheaders.authorization;
    }
  }
}, (nockDone) => {
  // Test code...
  nockDone();
});

After Hook

Process loaded scopes after fixture loading:

nock.back("api-test.json", {
  after: (scope) => {
    // Add additional configuration to loaded scopes
    scope.defaultReplyHeaders({
      "X-Test-Mode": "fixture"
    });
  }
}, (nockDone) => {
  // Test code...
  nockDone();
});

After Record Hook

Process recorded fixtures before saving:

nock.back.setMode("record");

nock.back("api-test.json", {
  afterRecord: (defs) => {
    // Process recorded definitions
    return defs.map(def => {
      // Remove sensitive data
      if (def.reqheaders) {
        delete def.reqheaders.authorization;
        delete def.reqheaders.cookie;
      }
      
      // Sanitize response data
      if (typeof def.response === "string") {
        try {
          const response = JSON.parse(def.response);
          if (response.email) {
            response.email = "user@example.com";
          }
          def.response = JSON.stringify(response);
        } catch (e) {
          // Leave non-JSON responses as-is
        }
      }
      
      return def;
    });
  }
}, (nockDone) => {
  // Test code that will be recorded...
  nockDone();
});

Custom Recorder Options

nock.back("api-test.json", {
  recorder: {
    output_objects: true,
    enable_reqheaders_recording: true,
    dont_print: true
  }
}, (nockDone) => {
  // Test code...
  nockDone();
});

Back Context

The context object provides information about loaded fixtures and verification methods.

interface BackContext {
  isLoaded: boolean;
  scopes: Scope[];
  assertScopesFinished(): void;
  query: InterceptorSurface[];
}

interface InterceptorSurface {
  method: string;
  uri: string;
  basePath: string;
  path: string;
  queries?: string;
  counter: number;
  body: string;
  statusCode: number;
  optional: boolean;
}

Using Context

const { nockDone, context } = await nock.back("complex-test.json");

console.log("Fixture loaded:", context.isLoaded);
console.log("Number of scopes:", context.scopes.length);
console.log("Interceptor details:", context.query);

try {
  // Run your tests...
  
  // Verify all interceptors were used
  context.assertScopesFinished();
  
} finally {
  nockDone();
}

Mode-Specific Workflows

Record Mode Workflow

// Set up recording
nock.back.fixtures = "./test/fixtures";
nock.back.setMode("record");

describe("Recording API Interactions", () => {
  it("should record user API calls", (done) => {
    nock.back("user-api.json", {
      afterRecord: (defs) => {
        console.log(`Recorded ${defs.length} interactions`);
        return defs;
      }
    }, (nockDone) => {
      // Make real API calls - these will be recorded
      Promise.all([
        fetch("https://api.example.com/users"),
        fetch("https://api.example.com/users/1"),
        fetch("https://api.example.com/users", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ name: "Alice" })
        })
      ]).then(() => {
        nockDone();
        done();
      });
    });
  });
});

Lockdown Mode Workflow

// Set up lockdown mode for CI/production tests
nock.back.fixtures = "./test/fixtures";
nock.back.setMode("lockdown");

describe("Lockdown Tests", () => {
  it("should use only existing fixtures", async () => {
    try {
      const { nockDone, context } = await nock.back("existing-fixture.json");
      
      // This will only work if the fixture exists
      const response = await fetch("https://api.example.com/users");
      const users = await response.json();
      
      expect(Array.isArray(users)).toBe(true);
      context.assertScopesFinished();
      nockDone();
      
    } catch (error) {
      if (error.message.includes("fixture")) {
        console.error("Fixture not found - run in record mode first");
      }
      throw error;
    }
  });
});

Update Mode Workflow

// Update existing fixtures with new data
nock.back.setMode("update");

describe("Update Fixtures", () => {
  it("should update existing fixture", (done) => {
    nock.back("user-api.json", {
      afterRecord: (defs) => {
        console.log("Updated fixture with new recordings");
        return defs;
      }
    }, (nockDone) => {
      // Make API calls - these will replace the existing fixture
      fetch("https://api.example.com/users/1")
        .then(() => {
          nockDone();
          done();
        });
    });
  });
});

Complete Example: Multi-Mode Test Suite

const nock = require("nock");

// Configure fixtures directory
nock.back.fixtures = "./test/fixtures";

// Set mode based on environment
const mode = process.env.NOCK_BACK_MODE || "dryrun";
nock.back.setMode(mode);

describe("User API Integration Tests", () => {
  beforeEach(() => {
    // Clean up before each test
    nock.cleanAll();
  });

  it("should handle user creation flow", async () => {
    const { nockDone, context } = await nock.back("user-creation.json", {
      before: (def) => {
        // Sanitize any recorded auth headers
        if (def.reqheaders && def.reqheaders.authorization) {
          def.reqheaders.authorization = "Bearer <redacted>";
        }
      },
      
      afterRecord: (defs) => {
        // Process recorded fixtures
        return defs.map(def => {
          // Remove sensitive response data
          if (typeof def.response === "string") {
            try {
              const response = JSON.parse(def.response);
              if (response.apiKey) delete response.apiKey;
              if (response.internalId) delete response.internalId;
              def.response = JSON.stringify(response);
            } catch (e) {
              // Leave non-JSON responses as-is
            }
          }
          return def;
        });
      }
    });

    try {
      // Test the user creation flow
      const createResponse = await fetch("https://api.example.com/users", {
        method: "POST",
        headers: { 
          "Content-Type": "application/json",
          "Authorization": "Bearer test-token"
        },
        body: JSON.stringify({ 
          name: "Alice", 
          email: "alice@example.com" 
        })
      });
      
      expect(createResponse.status).toBe(201);
      const newUser = await createResponse.json();
      expect(newUser.id).toBeDefined();

      // Verify user was created
      const getResponse = await fetch(`https://api.example.com/users/${newUser.id}`);
      expect(getResponse.status).toBe(200);
      const fetchedUser = await getResponse.json();
      expect(fetchedUser.name).toBe("Alice");

      // Verify all fixtures were used
      context.assertScopesFinished();
      
    } finally {
      nockDone();
    }
  });
});

Environment Configuration

Environment Variables

# Set mode via environment variable
export NOCK_BACK_MODE=record

# Run tests
npm test

# Change to lockdown for CI
export NOCK_BACK_MODE=lockdown
npm test

Package.json Scripts

{
  "scripts": {
    "test": "jest",
    "test:record": "NOCK_BACK_MODE=record jest",
    "test:update": "NOCK_BACK_MODE=update jest",
    "test:lockdown": "NOCK_BACK_MODE=lockdown jest"
  }
}

Best Practices

Fixture Organization

// Organize fixtures by feature/test suite
nock.back.fixtures = "./test/fixtures/user-api";

// Use descriptive fixture names
await nock.back("create-user-success.json");
await nock.back("create-user-validation-error.json");
await nock.back("get-user-not-found.json");

Sanitization

Always sanitize recorded fixtures:

const sanitizeFixture = (defs) => {
  return defs.map(def => {
    // Remove sensitive headers
    if (def.reqheaders) {
      delete def.reqheaders.authorization;
      delete def.reqheaders.cookie;
      delete def.reqheaders["x-api-key"];
    }
    
    // Sanitize response data
    if (typeof def.response === "string") {
      def.response = def.response
        .replace(/\b\d{16}\b/g, "XXXX-XXXX-XXXX-XXXX") // Credit cards
        .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, "user@example.com"); // Emails
    }
    
    return def;
  });
};

Error Handling

describe("API Tests with Error Handling", () => {
  it("should handle missing fixtures gracefully", async () => {
    try {
      const { nockDone } = await nock.back("missing-fixture.json");
      // Test code...
      nockDone();
    } catch (error) {
      if (nock.back.currentMode === "lockdown" && error.message.includes("fixture")) {
        console.warn("Fixture missing in lockdown mode - this is expected for new tests");
        // Handle missing fixture appropriately
      } else {
        throw error;
      }
    }
  });
});

Install with Tessl CLI

npx tessl i tessl/npm-nock

docs

fixture-testing.md

global-management.md

index.md

recording-playback.md

request-interception.md

request-matching.md

response-definition.md

tile.json