Skip to content

api‐zimic‐interceptor‐http

github-actions[bot] edited this page Nov 26, 2024 · 4 revisions

API reference: zimic/interceptor/http

Contents


This module exports resources to create HTTP interceptors and mock HTTP responses.

HttpInterceptor

HTTP interceptors provide the main API to handle HTTP requests and return mock responses. The methods, paths, status codes, parameters, and responses are statically-typed based on the service schema.

Each interceptor represents a service and can be used to mock its paths and methods.

httpInterceptor.create(options)

Creates an HTTP interceptor, the main interface to intercept HTTP requests and return responses. Learn more about declaring interceptor schemas.

Tip

If you are using TypeScript and have an OpenAPI v3 schema, you can use zimic typegen to automatically generate types for your interceptor schema!

Creating a local HTTP interceptor

A local interceptor is configured with type: 'local'. The baseURL represents the URL should be matched by this interceptor. Any request starting with the baseURL will be intercepted if a matching handler exists.

import { httpInterceptor } from 'zimic/interceptor/http';

interface User {
  username: string;
}

const interceptor = httpInterceptor.create<{
  '/users/:id': {
    GET: {
      response: {
        200: { body: User };
      };
    };
  };
}>({
  type: 'local',
  baseURL: 'http://localhost:3000',
});

Creating a remote HTTP interceptor

A remote interceptor is configured with type: 'remote'. The baseURL points to an interceptor server. Any request starting with the baseURL will be intercepted if a matching handler exists.

import { httpInterceptor } from 'zimic/interceptor/http';

interface User {
  username: string;
}

const interceptor = httpInterceptor.create<{
  '/users/:id': {
    GET: {
      response: {
        200: { body: User };
      };
    };
  };
}>({
  // The interceptor server is at http://localhost:4000
  // `/my-service` is a path to differentiate from other
  // interceptors using the same server
  type: 'remote',
  baseURL: 'http://localhost:4000/my-service',
});
Path discriminators in remote HTTP interceptors

A single interceptor server is perfectly capable of handling multiple interceptors and requests. Thus, additional paths are supported and might be necessary to differentiate between conflicting interceptors. If you may have multiple threads or processes applying mocks concurrently to the same interceptor server, it's important to keep the interceptor base URLs unique. Also, make sure that your application is considering the correct URL when making requests.

const interceptor = httpInterceptor.create<{
  // ...
}>({
  type: 'remote',
  // Declaring a base URL with a unique identifier to prevent conflicts
  baseURL: `http://localhost:4000/my-service-${crypto.randomUUID()}`,
});

// Your application should use this base URL when making requests
const baseURL = interceptor.baseURL();

Unhandled requests

When a request is not matched by any interceptor handlers, it is considered unhandled and will be logged to the console by default.

Tip

If you expected a request to be handled, but it was not, make sure that the interceptor base URL, path, method, and restrictions correctly match the request. Additionally, confirm that no errors occurred while creating the response.

In a local interceptor, unhandled requests can be either bypassed or rejected. Bypassed requests reach the real network, whereas rejected requests fail with an network error. The default behavior in local interceptors is to bypass unhandled requests.

On the other hand, remote interceptors and interceptor server always reject unhandled requests. This is because the unhandled requests have already reached the interceptor server, so there would be no way of bypassing them at this point.

You can override the default logging behavior per interceptor with onUnhandledRequest in httpInterceptor.create(options). onUnhandledRequest also accepts a function to dynamically determine which strategy to use for an unhandled request.

Example 1: Ignore all unhandled requests with no logging:
import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<Schema>({
  type: 'local',
  baseURL: 'http://localhost:3000',
  onUnhandledRequest: {
    action: 'bypass', // Allow unhandled requests to reach the real network
    log: false, // Do not log warnings about unhandled requests
  },
});
Example 2: Reject all unhandled requests with logging:
import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<Schema>({
  type: 'local',
  baseURL: 'http://localhost:3000',
  onUnhandledRequest: {
    action: 'reject', // Do not allow unhandled requests to reach the real network
    log: true, // Log warnings about unhandled requests
  },
});
Example 3: Dynamically ignore or reject unhandled requests:
import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<Schema>({
  type: 'local',
  baseURL: 'http://localhost:3000',
  onUnhandledRequest: async (request) => {
    const url = new URL(request.url);

    // Ignore only unhandled requests to /assets
    if (url.pathname.startsWith('/assets')) {
      return { action: 'bypass', log: false };
    }

    // Reject all other unhandled requests
    return { action: 'reject', log: true };
  },
});

If you want to override the default logging behavior for all interceptors, or requests that did not match any known base URL, you can use httpInterceptor.default.local.onUnhandledRequest or httpInterceptor.default.remote.onUnhandledRequest. Keep in mind that defining an onUnhandledRequest when creating an interceptor will take precedence over httpInterceptor.default.local.onUnhandledRequest and httpInterceptor.default.remote.onUnhandledRequest.

Example 4: Ignore all unhandled requests with no logging:
import { httpInterceptor } from 'zimic/interceptor/http';

httpInterceptor.default.local.onUnhandledRequest = {
  action: 'bypass',
  log: false,
};

httpInterceptor.default.remote.onUnhandledRequest = {
  action: 'reject',
  log: false,
};
Example 5: Reject all unhandled requests with logging:
import { httpInterceptor } from 'zimic/interceptor/http';

// For local interceptors:
httpInterceptor.default.local.onUnhandledRequest = {
  action: 'reject',
  log: true,
};

// For remote interceptors:
httpInterceptor.default.remote.onUnhandledRequest = {
  action: 'reject',
  log: true,
};
Example 6: Dynamically ignore or reject all unhandled requests:
import { httpInterceptor } from 'zimic/interceptor/http';

// For local interceptors:
httpInterceptor.default.local.onUnhandledRequest = (request) => {
  const url = new URL(request.url);

  // Ignore only unhandled requests to /assets
  if (url.pathname.startsWith('/assets')) {
    return { action: 'bypass', log: false };
  }

  // Reject all other unhandled requests
  return { action: 'reject', log: true };
};

// For remote interceptors:
httpInterceptor.default.remote.onUnhandledRequest = (request) => {
  const url = new URL(request.url);

  return {
    action: 'reject', // Reject all unhandled requests
    log: !url.pathname.startsWith('/assets'), // Log warnings for all unhandled requests except /assets
  };
};

Saving requests

The option saveRequests indicates whether request handlers should save their intercepted requests in memory and make them accessible through handler.requests().

This setting is configured per interceptor and is false by default. If set to true, each handler will keep track of their intercepted requests in memory.

Important

Saving the intercepted requests will lead to a memory leak if not accompanied by clearing of the interceptor or disposal of the handlers (i.e. garbage collection).

If you plan on accessing those requests, such as to assert them in your tests, set saveRequests to true and make sure to regularly clear the interceptor. A common practice is to call interceptor.clear() after each test.

See Testing for an example of how to manage the lifecycle of interceptors in your tests.

import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<Schema>({
  type: 'local',
  baseURL: 'http://localhost:3000',
  saveRequests: true,
});

Tip

If you use an interceptor both in tests and as a standalone mock server, consider setting saveRequests based on an environment variable. This allows you to access the requests in tests, while preventing memory leaks in long-running mock servers.

import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<Schema>({
  type: 'local',
  baseURL: 'http://localhost:3000',
  saveRequests: process.env.NODE_ENV === 'test',
});

HTTP interceptor.start()

Starts the interceptor. Only interceptors that are running will intercept requests.

await interceptor.start();

When targeting a browser environment with a local interceptor, make sure to follow the client-side post-install guide before starting your interceptors.

HTTP interceptor.stop()

Stops the interceptor. Stopping an interceptor will also clear its registered handlers and responses.

await interceptor.stop();

HTTP interceptor.isRunning()

Returns whether the interceptor is currently running and ready to use.

const isRunning = interceptor.isRunning();

HTTP interceptor.baseURL()

Returns the base URL of the interceptor.

const baseURL = interceptor.baseURL();

HTTP interceptor.platform()

Returns the platform used by the interceptor (browser or node).

const platform = interceptor.platform();

HTTP interceptor.<method>(path)

Creates an HttpRequestHandler for the given method and path. The path and method must be declared in the interceptor schema.

The supported methods are: get, post, put, patch, delete, head, and options.

When using a remote interceptor, creating a handler is an asynchronous operation, so you need to await it. You can also chain any number of operations and apply them by awaiting the handler.

Using a local interceptor
import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<{
  '/users': {
    GET: {
      response: {
        200: { body: User[] };
      };
    };
  };
}>({
  type: 'local',
  baseURL: 'http://localhost:3000',
});

const listHandler = interceptor.get('/users').respond({
  status: 200
  body: [{ username: 'diego-aquino' }],
});
Using a remote interceptor
import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<{
  '/users': {
    GET: {
      response: {
        200: { body: User[] };
      };
    };
  };
}>({
  type: 'remote',
  baseURL: 'http://localhost:4000/my-service',
});

const listHandler = await interceptor.get('/users').respond({
  status: 200
  body: [{ username: 'diego-aquino' }],
});

Path parameters

Paths with parameters are supported, such as /users/:id. Even when using a computed path (e.g. /users/1), the original path is automatically inferred, guaranteeing type safety.

import { httpInterceptor } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<{
  '/users/:id': {
    PUT: {
      request: {
        body: { username: string };
      };
      response: {
        204: {};
      };
    };
  };
}>({
  type: 'local',
  baseURL: 'http://localhost:3000',
});

interceptor.get('/users/:id'); // Matches any id
interceptor.get(`/users/${1}`); // Only matches id 1

request.pathParams contains the parsed path parameters of a request and have their type automatically inferred from the path string. For example, the path /users/:userId will result in a request.pathParams of type { userId: string }.

Using a local interceptor
const updateHandler = interceptor.put('/users/:id').respond((request) => {
  console.log(request.pathParams); // { id: '1' }

  return {
    status: 200,
    body: { username: 'diego-aquino' },
  };
});

await fetch('http://localhost:3000/users/1', { method: 'PUT' });
Using a remote interceptor
const updateHandler = await interceptor.put('/users/:id').respond((request) => {
  console.log(request.pathParams); // { id: '1' }

  return {
    status: 200,
    body: { username: 'diego-aquino' },
  };
});

await fetch('http://localhost:3000/users/1', { method: 'PUT' });

HTTP interceptor.clear()

Clears all of the HttpRequestHandler instances created by this interceptor, including their registered responses and intercepted requests. After calling this method, the interceptor will no longer intercept any requests until new mock responses are registered.

This method is useful to reset the interceptor mocks between tests.

Using a local interceptor
interceptor.clear();
Using a remote interceptor
await interceptor.clear();

HttpInterceptor utility types

InferHttpInterceptorSchema

Infers the schema of an HTTP interceptor.

import { httpInterceptor, type InferHttpInterceptorSchema } from 'zimic/interceptor/http';

const interceptor = httpInterceptor.create<{
  '/users': {
    GET: {
      response: { 200: { body: User[] } };
    };
  };
}>({
  type: 'local',
  baseURL: 'http://localhost:3000',
});

type Schema = InferHttpInterceptorSchema<typeof interceptor>;
// {
//   '/users': {
//     GET: {
//       response: { 200: { body: User[] } };
//     };
//   };
// }

HttpRequestHandler

HTTP request handlers allow declaring HTTP responses to return for intercepted requests. They also keep track of the intercepted requests and their responses, which can be used to check if the requests your application has made are correct.

When multiple handlers match the same method and path, the last created with interceptor.<method>(path) will be used.

HTTP handler.method()

Returns the method that matches a handler.

Using a local interceptor
const creationHandler = interceptor.post('/users');
const method = creationHandler.method();
console.log(method); // 'POST'
Using a remote interceptor
const handler = await interceptor.post('/users');
const method = handler.method();
console.log(method); // 'POST'

HTTP handler.path()

Returns the path that matches a handler. The base URL of the interceptor is not included, but it is used when matching requests.

Using a local interceptor
const listHandler = interceptor.get('/users');
const path = listHandler.path();
console.log(path); // '/users'
Using a remote interceptor
const handler = await interceptor.get('/users');
const path = handler.path();
console.log(path); // '/users'

HTTP handler.with(restriction)

Declares a restriction to intercepted requests. headers, searchParams, and body are supported to limit which requests will match the handler and receive the mock response. If multiple restrictions are declared, either in a single object or with multiple calls to handler.with(), all of them must be met, essentially creating an AND condition.

Static restrictions

Declaring restrictions for headers:
Using a local interceptor
const creationHandler = interceptor
  .get('/users')
  .with({
    headers: { authorization: `Bearer ${token}` },
  })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Using a remote interceptor
const creationHandler = await interceptor
  .get('/users')
  .with({
    headers: { authorization: `Bearer ${token}` },
  })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });

An equivalent alternative using HttpHeaders:

Using a local interceptor
import { type HttpSchema, HttpHeaders } from 'zimic/http';

type UserListHeaders = HttpSchema.Headers<{
  authorization: string;
}>;

const headers = new HttpHeaders<UserListHeaders>({
  authorization: `Bearer ${token}`,
});

const creationHandler = interceptor
  .get('/users')
  .with({ headers })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Using a remote interceptor
import { type HttpSchema, HttpHeaders } from 'zimic/http';

type UserListHeaders = HttpSchema.Headers<{
  authorization: string;
}>;

const headers = new HttpHeaders<UserListHeaders>({
  authorization: `Bearer ${token}`,
});

const creationHandler = await interceptor
  .get('/users')
  .with({ headers })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Declaring restrictions for search params:
Using a local interceptor
const creationHandler = interceptor
  .get('/users')
  .with({
    searchParams: { username: 'diego-aquino' },
  })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Using a remote interceptor
const creationHandler = await interceptor
  .get('/users')
  .with({
    searchParams: { username: 'diego-aquino' },
  })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });

An equivalent alternative using HttpSearchParams:

Using a local interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserListSearchParams = HttpSchema.SearchParams<{
  username?: string;
}>;

const searchParams = new HttpSearchParams<UserListSearchParams>({
  username: 'diego-aquino',
});

const creationHandler = interceptor
  .get('/users')
  .with({ searchParams })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Using a remote interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserListSearchParams = HttpSchema.SearchParams<{
  username?: string;
}>;

const searchParams = new HttpSearchParams<UserListSearchParams>({
  username: 'diego-aquino',
});

const creationHandler = await interceptor
  .get('/users')
  .with({ searchParams })
  .respond({
    status: 200,
    body: [{ username: 'diego-aquino' }],
  });
Declaring restrictions for a JSON body:
Using a local interceptor
const creationHandler = interceptor
  .post('/users')
  .with({
    body: { username: 'diego-aquino' },
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
const creationHandler = await interceptor
  .post('/users')
  .with({
    body: { username: 'diego-aquino' },
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });

For JSON bodies to be correctly parsed, make sure that the intercepted requests have the header content-type: application/json.

Declaring restrictions for a form data body:
Using a local interceptor
import { type HttpSchema, HttpFormData } from 'zimic/http';

type UserCreationData = HttpSchema.FormData<{
  username: string;
  profilePicture: Blob;
}>;

const formData = new HttpFormData<UserCreationData>();
formData.append('username', 'diego-aquino');
formData.append(
  'profilePicture',
  new File(['content'], 'profile.png', {
    type: 'image/png',
  }),
);

const creationHandler = interceptor
  .post('/users')
  .with({
    body: formData,
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
import { type HttpSchema, HttpFormData } from 'zimic/http';

type UserCreationData = HttpSchema.FormData<{
  username: string;
  profilePicture: Blob;
}>;

const formData = new HttpFormData<UserCreationData>();
formData.append('username', 'diego-aquino');
formData.append(
  'profilePicture',
  new File(['content'], 'profile.png', {
    type: 'image/png',
  }),
);

const creationHandler = await interceptor
  .post('/users')
  .with({
    body: formData,
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });

For form data bodies to be correctly parsed, make sure that the intercepted requests have the header content-type: multipart/form-data.

Declaring restrictions for a blob body:
Using a local interceptor
const creationHandler = interceptor
  .post('/users')
  .with({
    body: new Blob(['content'], {
      type: 'application/octet-stream',
    }),
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
const creationHandler = await interceptor
  .post('/users')
  .with({
    body: new Blob(['content'], {
      type: 'application/octet-stream',
    }),
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });

For blob bodies to be correctly parsed, make sure that the intercepted requests have the header content-type indicating a binary data, such as application/octet-stream, image/png, audio/mp3, etc.

Declaring restrictions for a plain text body:
Using a local interceptor
const creationHandler = interceptor
  .post('/users')
  .with({
    body: 'content',
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
const creationHandler = await interceptor
  .post('/users')
  .with({
    body: 'content',
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Declaring restrictions for search params (x-www-form-urlencoded) body:
Using a local interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserGetByIdSearchParams = HttpSchema.SearchParams<{
  username: string;
}>;

const searchParams = new HttpSearchParams<UserGetByIdSearchParams>({
  username: 'diego-aquino',
});

const creationHandler = interceptor
  .post('/users')
  .with({
    body: searchParams,
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserGetByIdSearchParams = HttpSchema.SearchParams<{
  username: string;
}>;

const searchParams = new HttpSearchParams<UserGetByIdSearchParams>({
  username: 'diego-aquino',
});

const creationHandler = await interceptor
  .post('/users')
  .with({
    body: searchParams,
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });

For plain text bodies to be correctly parsed, make sure that the intercepted requests have the header content-type indicating a plain text, such as text/plain.

By default, restrictions use exact: false, meaning that any request containing the declared restrictions will match the handler, regardless of having more properties or values. In the examples above, requests with more properties in the headers, search params, or body would still match the restrictions.

If you want to match only requests with the exact values declared, you can use exact: true:

Using a local interceptor
const creationHandler = interceptor
  .post('/users')
  .with({
    headers: { 'content-type': 'application/json' },
    body: { username: 'diego-aquino' },
    exact: true, // Only requests with these exact headers and body will match
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });
Using a remote interceptor
const creationHandler = await interceptor
  .post('/users')
  .with({
    headers: { 'content-type': 'application/json' },
    body: { username: 'diego-aquino' },
    exact: true, // Only requests with these exact headers and body will match
  })
  .respond({
    status: 201,
    body: { username: 'diego-aquino' },
  });

Computed restrictions

A function is also supported to declare restrictions in case they are dynamic. Learn more about the request object at Intercepted HTTP resources.

Using a local interceptor
const creationHandler = interceptor
  .post('/users')
  .with((request) => {
    const accept = request.headers.get('accept');
    return accept !== null && accept.startsWith('application');
  })
  .respond({
    status: 201,
    body: [{ username: 'diego-aquino' }],
  });
Using a remote interceptor
const creationHandler = await interceptor
  .post('/users')
  .with((request) => {
    const accept = request.headers.get('accept');
    return accept !== null && accept.startsWith('application');
  })
  .respond({
    status: 201,
    body: [{ username: 'diego-aquino' }],
  });

The function should return a boolean: true if the request matches the handler and should receive the mock response; false otherwise.

HTTP handler.respond(declaration)

Declares a response to return for matched intercepted requests.

When the handler matches a request, it will respond with the given declaration. The response type is statically validated against the schema of the interceptor.

Static responses

Declaring responses with JSON body:
Using a local interceptor
const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});
Declaring responses with form data body:
Using a local interceptor
import { type HttpSchema, HttpFormData } from 'zimic/http';

type UserGetByIdData = HttpSchema.FormData<{
  username: string;
  profilePicture: Blob;
}>;

const formData = new HttpFormData<UserGetByIdData>();
formData.append('username', 'diego-aquino');
formData.append(
  'profilePicture',
  new File(['content'], 'profile.png', {
    type: 'image/png',
  }),
);

const listHandler = interceptor.get('/users/:id').respond({
  status: 200,
  body: formData,
});
Using a remote interceptor
import { type HttpSchema, HttpFormData } from 'zimic/http';

type UserGetByIdData = HttpSchema.FormData<{
  username: string;
  profilePicture: Blob;
}>;

const formData = new HttpFormData<UserGetByIdData>();
formData.append('username', 'diego-aquino');
formData.append(
  'profilePicture',
  new File(['content'], 'profile.png', {
    type: 'image/png',
  }),
);

const listHandler = await interceptor.get('/users/:id').respond({
  status: 200,
  body: formData,
});
Declaring responses with blob body:
Using a local interceptor
const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: new Blob(['content'], {
    type: 'application/octet-stream',
  }),
});
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: new Blob(['content'], {
    type: 'application/octet-stream',
  }),
});
Declaring responses with plain text body:
Using a local interceptor
const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: 'content',
});
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: 'content',
});
Declaring responses with search params (x-www-form-urlencoded) body:
Using a local interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserGetByIdSearchParams = HttpSchema.SearchParams<{
  username: string;
}>;

const searchParams = new HttpSearchParams<UserGetByIdSearchParams>({
  username: 'diego-aquino',
});

const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: searchParams,
});
Using a remote interceptor
import { type HttpSchema, HttpSearchParams } from 'zimic/http';

type UserGetByIdSearchParams = HttpSchema.SearchParams<{
  username: string;
}>;

const searchParams = new HttpSearchParams<UserGetByIdSearchParams>({
  username: 'diego-aquino',
});

const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: searchParams,
});

Computed responses

A function is also supported to declare a response in case it is dynamic. Learn more about the request object at Intercepted HTTP resources.

Using a local interceptor
const listHandler = interceptor.get('/users').respond((request) => {
  const username = request.searchParams.get('username');

  if (!username) {
    return { status: 400 };
  }

  return {
    status: 200,
    body: [{ username }],
  };
});
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond((request) => {
  const username = request.searchParams.get('username');

  if (!username) {
    return { status: 400 };
  }

  return {
    status: 200,
    body: [{ username }],
  };
});

HTTP handler.bypass()

Clears any response declared with handler.respond(declaration), making the handler stop matching requests. The next handler, created before this one, that matches the same method and path will be used if present. If not, the requests of the method and path will not be intercepted.

To make the handler match requests again, register a new response with handler.respond(declaration).

This method is useful to skip a handler. It is more gentle than handler.clear(), as it only removed the response, keeping restrictions and intercepted requests.

Using a local interceptor
const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: [],
});

const otherListHandler = interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});

otherListHandler.bypass();
// Now, requests GET /users will match `listHandler` and receive an empty array
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: [],
});

const otherListHandler = await interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});

await otherListHandler.bypass();
// Now, requests GET /users will match `listHandler` and receive an empty array

HTTP handler.clear()

Clears any response declared with handler.respond(declaration), restrictions declared with handler.with(restriction), and intercepted requests, making the handler stop matching requests. The next handler, created before this one, that matches the same method and path will be used if present. If not, the requests of the method and path will not be intercepted.

To make the handler match requests again, register a new response with handler.respond().

This method is useful to reset handlers to a clean state between tests. It is more aggressive than handler.bypass(), as it also clears restrictions and intercepted requests.

Using a local interceptor
const listHandler = interceptor.get('/users').respond({
  status: 200,
  body: [],
});

const otherListHandler = interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});

otherListHandler.clear();
// Now, requests GET /users will match `listHandler` and receive an empty array

otherListHandler.requests(); // Now empty
Using a remote interceptor
const listHandler = await interceptor.get('/users').respond({
  status: 200,
  body: [],
});

const otherListHandler = await interceptor.get('/users').respond({
  status: 200,
  body: [{ username: 'diego-aquino' }],
});

await otherListHandler.clear();
// Now, requests GET /users will match `listHandler` and receive an empty array

await otherListHandler.requests(); // Now empty

HTTP handler.requests()

Returns the intercepted requests that matched this handler, along with the responses returned to each of them. This is useful for testing that the correct requests were made by your application. Learn more about the request and response objects at Intercepted HTTP resources.

Important

This method can only be used if saveRequests was set to true when creating the interceptor. See Saving intercepted requests for more information.

Using a local interceptor
const updateHandler = interceptor.put('/users/:id').respond((request) => {
  const newUsername = request.body.username;
  return {
    status: 200,
    body: [{ username: newUsername }],
  };
});

await fetch(`http://localhost:3000/users/${1}`, {
  method: 'PUT',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ username: 'new' }),
});

const updateRequests = await updateHandler.requests();
expect(updateRequests).toHaveLength(1);
expect(updateRequests[0].pathParams).toEqual({ id: '1' });
expect(updateRequests[0].body).toEqual({ username: 'new' });
expect(updateRequests[0].response.status).toBe(200);
expect(updateRequests[0].response.body).toEqual([{ username: 'new' }]);
Using a remote interceptor
const updateHandler = await interceptor.put('/users/:id').respond((request) => {
  const newUsername = request.body.username;
  return {
    status: 200,
    body: [{ username: newUsername }],
  };
});

await fetch(`http://localhost:3000/users/${1}`, {
  method: 'PUT',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ username: 'new' }),
});

const updateRequests = await updateHandler.requests();
expect(updateRequests).toHaveLength(1);
expect(updateRequests[0].pathParams).toEqual({ id: '1' });
expect(updateRequests[0].body).toEqual({ username: 'new' });
expect(updateRequests[0].response.status).toBe(200);
expect(updateRequests[0].response.body).toEqual([{ username: 'new' }]);

Intercepted HTTP resources

The intercepted requests and responses are typed based on their interceptor schema. They are available as simplified objects based on the Request and Response web APIs. body contains the parsed body, while typed headers, path params and search params are in headers, pathParams, and searchParams, respectively.

The body is automatically parsed based on the header content-type of the request or response. The following table shows how each type is parsed, where * indicates any other resource that does not match the previous types:

content-type Parsed to
application/json JSON
application/xml String
application/x-www-form-urlencoded HttpSearchParams
application/* (others) Blob
multipart/form-data HttpFormData
multipart/* (others) Blob
text/* String
image/* Blob
audio/* Blob
font/* Blob
video/* Blob
*/* (others) JSON if possible, otherwise String

If no content-type exists or it is unknown, Zimic tries to parse the body as JSON and falls back to plain text if it fails.

If you need access to the original Request and Response objects, you can use the request.raw property:

console.log(request.raw); // Request{}
console.log(request.response.raw); // Response{}
Clone this wiki locally