HTTP server mocking and expectations library for Node.js testing environments
67
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.
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 operates in different modes that control how fixtures are handled:
type BackMode = 'wild' | 'dryrun' | 'record' | 'update' | 'lockdown';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";Configure where fixtures are stored:
// Set fixture directory
nock.back.fixtures = "./test/fixtures";
// All fixture files will be created/loaded from this directoryback(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();
});
});
});
});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();
}
});
});interface BackOptions {
before?: (def: Definition) => void;
after?: (scope: Scope) => void;
afterRecord?: (defs: Definition[]) => Definition[] | string;
recorder?: RecorderOptions;
}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();
});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();
});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();
});nock.back("api-test.json", {
recorder: {
output_objects: true,
enable_reqheaders_recording: true,
dont_print: true
}
}, (nockDone) => {
// Test code...
nockDone();
});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;
}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();
}// 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();
});
});
});
});// 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 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();
});
});
});
});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();
}
});
});# 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{
"scripts": {
"test": "jest",
"test:record": "NOCK_BACK_MODE=record jest",
"test:update": "NOCK_BACK_MODE=update jest",
"test:lockdown": "NOCK_BACK_MODE=lockdown jest"
}
}// 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");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;
});
};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-nockdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10