HTTP server mocking and expectations library for Node.js testing environments
67
This document covers advanced request matching capabilities including headers, query parameters, request bodies, and path filtering.
Match requests based on query string parameters using the query method.
interface Interceptor {
query(matcher: QueryMatcher): this;
}
type QueryMatcher =
| boolean
| string
| DataMatcherMap
| URLSearchParams
| ((parsedObj: ParsedUrlQuery) => boolean);// Match any query parameters (or none)
nock("https://api.example.com")
.get("/users")
.query(true)
.reply(200, []);
// Match only requests with no query parameters
nock("https://api.example.com")
.get("/users")
.query(false)
.reply(200, []);// Match exact query string
nock("https://api.example.com")
.get("/users")
.query("page=1&limit=10")
.reply(200, []);
// Order doesn't matter
nock("https://api.example.com")
.get("/users")
.query("limit=10&page=1") // Will also match page=1&limit=10
.reply(200, []);// Match specific parameters with exact values
nock("https://api.example.com")
.get("/users")
.query({ page: "1", limit: "10" })
.reply(200, []);
// Use matchers for flexible matching
nock("https://api.example.com")
.get("/users")
.query({
page: /^\d+$/, // Any numeric page
limit: (value) => parseInt(value) <= 100 // Limit validation
})
.reply(200, []);const params = new URLSearchParams();
params.set("category", "electronics");
params.set("inStock", "true");
nock("https://api.example.com")
.get("/products")
.query(params)
.reply(200, []);nock("https://api.example.com")
.get("/search")
.query((queryObj) => {
// Match if query contains 'q' parameter and it's not empty
return queryObj.q && queryObj.q.length > 0;
})
.reply(200, { results: [] });Match requests based on HTTP headers using various header matching methods.
interface Scope {
matchHeader(name: string, value: RequestHeaderMatcher): this;
}Apply header matching to all interceptors in a scope:
// All requests in this scope must have this header
const scope = nock("https://api.example.com")
.matchHeader("authorization", "Bearer token123")
.matchHeader("content-type", "application/json");
scope.get("/users").reply(200, []);
scope.post("/users").reply(201, { id: 1 });interface Interceptor {
matchHeader(name: string, value: RequestHeaderMatcher): this;
}
type RequestHeaderMatcher = string | RegExp | ((fieldValue: string) => boolean);Apply header matching to specific interceptors:
// Exact string match
nock("https://api.example.com")
.get("/users")
.matchHeader("accept", "application/json")
.reply(200, []);
// RegExp match
nock("https://api.example.com")
.get("/users")
.matchHeader("user-agent", /Chrome/)
.reply(200, []);
// Function match
nock("https://api.example.com")
.get("/users")
.matchHeader("authorization", (value) => {
return value.startsWith("Bearer ") && value.length > 20;
})
.reply(200, []);interface Interceptor {
basicAuth(options: { user: string; pass?: string }): this;
}Match HTTP Basic Authentication credentials:
// With username and password
nock("https://api.example.com")
.get("/secure")
.basicAuth({ user: "admin", pass: "secret" })
.reply(200, { message: "Authorized" });
// Username only
nock("https://api.example.com")
.get("/user-only")
.basicAuth({ user: "admin" })
.reply(200, { message: "User authenticated" });Match requests based on their body content. This is commonly used with POST, PUT, and PATCH requests.
// Exact string match
nock("https://api.example.com")
.post("/users", "name=Alice&email=alice@example.com")
.reply(201, { id: 1 });
// Exact JSON object match
nock("https://api.example.com")
.post("/users", { name: "Alice", email: "alice@example.com" })
.reply(201, { id: 1 });
// Buffer match
const bodyBuffer = Buffer.from("binary data");
nock("https://api.example.com")
.post("/upload", bodyBuffer)
.reply(200, { uploaded: true });// Match body with regex
nock("https://api.example.com")
.post("/users", /Alice/)
.reply(201, { id: 1 });
// Match JSON structure with regex
nock("https://api.example.com")
.post("/users", {
name: /^[A-Z][a-z]+$/, // Capitalized name
email: /@example\.com$/ // Example.com email
})
.reply(201, { id: 1 });type RequestBodyMatcher =
| string
| Buffer
| RegExp
| DataMatcherArray
| DataMatcherMap
| ((body: any) => boolean);nock("https://api.example.com")
.post("/users", (body) => {
const user = JSON.parse(body);
return user.name && user.email && user.email.includes("@");
})
.reply(201, { id: 1 });Transform request paths before matching, useful for handling dynamic segments or normalizing paths.
interface Scope {
filteringPath(regex: RegExp, replace: string): this;
}// Replace user IDs with a placeholder
const scope = nock("https://api.example.com")
.filteringPath(/\/users\/\d+/, "/users/123");
// This will match requests to /users/456, /users/789, etc.
scope.get("/users/123").reply(200, { id: 123, name: "User" });interface Scope {
filteringPath(fn: (path: string) => string): this;
}const scope = nock("https://api.example.com")
.filteringPath((path) => {
// Normalize paths by removing query parameters
return path.split("?")[0];
});
// This interceptor will match /users regardless of query params
scope.get("/users").reply(200, []);Transform request bodies before matching, useful for normalizing data or ignoring certain fields.
interface Scope {
filteringRequestBody(regex: RegExp, replace: string): this;
}// Replace timestamps with a fixed value
const scope = nock("https://api.example.com")
.filteringRequestBody(/"timestamp":\d+/, '"timestamp":1234567890');
scope.post("/events", { event: "login", timestamp: 1234567890 })
.reply(200, { recorded: true });
// Will match requests with any timestamp valueinterface Scope {
filteringRequestBody(
fn: (body: string, recordedBody: string) => string
): this;
}const scope = nock("https://api.example.com")
.filteringRequestBody((body, recordedBody) => {
// Remove dynamic fields before matching
const parsed = JSON.parse(body);
delete parsed.timestamp;
delete parsed.requestId;
return JSON.stringify(parsed);
});
scope.post("/users", '{"name":"Alice","email":"alice@example.com"}')
.reply(201, { id: 1 });
// Will match requests regardless of timestamp/requestId fieldsAdvanced data structures for flexible matching in query parameters and request bodies.
type DataMatcher =
| boolean
| number
| string
| null
| undefined
| RegExp
| DataMatcherArray
| DataMatcherMap;
interface DataMatcherArray extends ReadonlyArray<DataMatcher> {}
interface DataMatcherMap {
[key: string]: DataMatcher;
}// Nested object matching with various matcher types
nock("https://api.example.com")
.post("/complex", {
user: {
name: /^[A-Z]/, // Name starts with capital letter
age: (age) => age >= 18 && age <= 100, // Age validation
preferences: {
theme: "dark",
notifications: true
}
},
tags: ["user", /^pref_/], // Array with string and regex
metadata: null,
active: true
})
.reply(201, { success: true });const apiScope = nock("https://api.example.com")
.matchHeader("authorization", /^Bearer .+/)
.matchHeader("content-type", "application/json")
.filteringPath(/\/v\d+/, "/v1"); // Normalize API versions
// Create user with validation
apiScope
.post("/v1/users", (body) => {
const user = JSON.parse(body);
return user.name && user.email && user.email.includes("@");
})
.reply(201, { id: 1, name: "Alice", email: "alice@example.com" });
// Search users with pagination
apiScope
.get("/v1/users")
.query({
page: /^\d+$/,
limit: (limit) => parseInt(limit) <= 100,
search: true // Any search term
})
.reply(200, { users: [], total: 0, page: 1 });nock("https://api.example.com")
.post("/upload", (body) => {
// Check if body contains multipart form data
return body.includes("Content-Disposition: form-data");
})
.matchHeader("content-type", /^multipart\/form-data/)
.reply(200, {
uploadId: "abc123",
status: "uploaded"
});nock("https://api.example.com")
.post("/graphql", {
query: /query\s+GetUser/,
variables: {
userId: /^\d+$/
}
})
.matchHeader("content-type", "application/json")
.reply(200, {
data: {
user: { id: "1", name: "Alice" }
}
});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