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

request-matching.mddocs/

Request Matching

This document covers advanced request matching capabilities including headers, query parameters, request bodies, and path filtering.

Query Parameter Matching

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);

Boolean Matching

// 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, []);

String Matching

// 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, []);

Object Matching

// 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, []);

URLSearchParams Matching

const params = new URLSearchParams();
params.set("category", "electronics");
params.set("inStock", "true");

nock("https://api.example.com")
  .get("/products")
  .query(params)
  .reply(200, []);

Function-Based Matching

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: [] });

Header Matching

Match requests based on HTTP headers using various header matching methods.

Scope-Level Header Matching

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 });

Interceptor-Level Header Matching

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, []);

Basic Authentication

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" });

Request Body Matching

Match requests based on their body content. This is commonly used with POST, PUT, and PATCH requests.

Exact Body Matching

// 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 });

RegExp Body Matching

// 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 });

Function-Based Body Matching

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 });

Path Filtering

Transform request paths before matching, useful for handling dynamic segments or normalizing paths.

RegExp Path Filtering

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" });

Function-Based Path Filtering

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, []);

Request Body Filtering

Transform request bodies before matching, useful for normalizing data or ignoring certain fields.

RegExp Body Filtering

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 value

Function-Based Body Filtering

interface 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 fields

Data Matching Types

Advanced 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;
}

Complex Object Matching

// 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 });

Examples: Complete Request Matching

API with Authentication and Validation

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 });

File Upload Endpoint

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" 
  });

GraphQL Endpoint

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-nock

docs

fixture-testing.md

global-management.md

index.md

recording-playback.md

request-interception.md

request-matching.md

response-definition.md

tile.json