-
Notifications
You must be signed in to change notification settings - Fork 2
getting‐started
- 1. Requirements
- 2. Installation
- 3. Post-install
- 4. Choose your method to intercept requests
- 5. Create your first interceptor
- 6. Next steps
- If you are on client-side:
- If you are on server-side:
- Node >= 18.13.0
-
Bun(🚧 coming soon 🚧) -
Deno( 🚧 coming soon 🚧)
-
TypeScript >= 4.7
- If you plan on using
zimic typegen
, we recommend TypeScript >= 5.0.
- If you plan on using
-
JavaScript >= ES6
- Zimic is fully functional on JavaScript, but consider using TypeScript for improved type safety and editor support.
If you are using TypeScript, we recommend enabling strict
in your tsconfig.json
:
{
// ...
"compilerOptions": {
// ...
"strict": true,
},
}
Zimic is available on npm.
Manager | Command |
---|---|
npm | npm install zimic --save-dev |
pnpm | pnpm add zimic --dev |
yarn | yarn add zimic --dev |
bun | bun add zimic --dev |
We also canary releases under the tag canary
, containing the latest features and bug fixes:
Manager | Command |
---|---|
npm | npm install zimic@canary --save-dev |
pnpm | pnpm add zimic@canary --dev |
yarn | yarn add zimic@canary --dev |
bun | bun add zimic@canary --dev |
If you plan to use local interceptors and run Zimic in a browser, you must first initialize a mock service worker in your public directory. After that, you are ready to start mocking!
No additional configuration is required for server-side applications!
Zimic interceptors support two types of execution: local
and remote
.
Tip
Multiple interceptors with different types are perfectly possible in the same application. However, keep in mind that local interceptors have precedence over remote interceptors.
When an interceptor is local
, Zimic uses MSW to intercept requests in the same
process as your application. This is the simplest way to start mocking requests and does not require any server setup.
Our Vitest, Jest, and Next.js Pages Router examples use local interceptors.
- Testing: If you run your application in the same process as your tests. This is common when using unit and integration test runners such as Jest and Vitest.
- Development: If you want to mock requests in your development environment without setting up a server. This is useful when you need a backend that is not ready or available.
Important
All mocking operations in local interceptor are synchronous. There's no need to await
them before making
requests.
When an interceptor is remote
, Zimic uses a dedicated local interceptor server to handle requests.
This opens up more possibilities for mocking, such as handling requests from multiple applications. It is also more
robust because it uses a regular HTTP server and does not depend on local interception algorithms.
Our Playwright and Next.js App Router examples use remote interceptors.
- Testing: If you do not run your application in the same process as your tests. When using Cypress, Playwright, or other end-to-end testing tools, this is generally the case because the test runner and the application run in separate processes. This might also happen in more complex setups with unit and integration test runners, such as testing a server that is running in another process, terminal, or machine.
-
Development: If you want your mocked responses to be accessible by other processes in your local network (e.g.
browser, app,
curl
) . A common scenario is to create a mock server along with a script to apply the mocks. After started, the server can be accessed by other applications and return mock responses.
Important
All mocking operations in remote interceptors are asynchronous. Make sure to await
them before making requests.
Many code snippets in this wiki show examples with a local and a remote interceptor. Generally, the remote snippets
differ only by adding await
where necessary.
If you are using typescript-eslint
, a handy rule is
@typescript-eslint/no-floating-promises
. It checks
promises appearing to be unhandled, which is helpful to indicate missing await
's in remote interceptor operations.
-
To start using Zimic, create your first HTTP interceptor:
Using a local interceptor
import { type HttpSchema } from 'zimic/http'; import { httpInterceptor } from 'zimic/interceptor/http'; // Declare your types: interface User { username: string; } interface RequestError { code: string; message: string; } // Declare your HTTP schema: // https://bit.ly/zimic-interceptor-http-schemas type MySchema = HttpSchema<{ '/users': { POST: { request: { body: User }; response: { 201: { body: User }; 400: { body: RequestError }; 409: { body: RequestError }; }; }; GET: { request: { headers: { authorization: string }; searchParams: { username?: string; limit?: `${number}`; }; }; response: { 200: { body: User[] }; 400: { body: RequestError }; 401: { body: RequestError }; }; }; }; '/users/:userId': { PATCH: { request: { headers: { authorization: string }; body: Partial<User>; }; response: { 204: {}; 400: { body: RequestError }; }; }; }; }>; // Create your interceptor: // https://bit.ly/zimic-interceptor-http#httpinterceptorcreateoptions const myInterceptor = httpInterceptor.create<MySchema>({ type: 'local', baseURL: 'http://localhost:3000', saveRequests: true, // Allow access to `handler.requests()` });
Using a remote interceptor
import { type HttpSchema } from 'zimic/http'; import { httpInterceptor } from 'zimic/interceptor/http'; // Declare your types interface User { username: string; } interface RequestError { code: string; message: string; } // Declare your HTTP schema: // https://bit.ly/zimic-interceptor-http-schemas type MySchema = HttpSchema<{ '/users': { POST: { request: { body: User }; response: { 201: { body: User }; 400: { body: RequestError }; 409: { body: RequestError }; }; }; GET: { request: { headers: { authorization: string }; searchParams: { username?: string; limit?: `${number}`; }; }; response: { 200: { body: User[] }; 400: { body: RequestError }; 401: { body: RequestError }; }; }; }; '/users/:userId': { PATCH: { request: { headers: { authorization: string }; body: Partial<User>; }; response: { 204: {}; 400: { body: RequestError }; }; }; }; }>; // Create your interceptor: // https://bit.ly/zimic-interceptor-http#httpinterceptorcreateoptions const myInterceptor = httpInterceptor.create<MySchema>({ type: 'remote', // The interceptor server is at http://localhost:4000 baseURL: 'http://localhost:4000/my-service', saveRequests: true, // Allow access to `handler.requests()` });
In this example, we're creating an interceptor for a service supporting
POST
andGET
requests to/users
. A successful response after creating a user is aUser
object, whereas listing users returns an array ofUser
objects. Errors are represented by aRequestError
object.You can also use
zimic typegen
to automatically generate types for your interceptor schema. -
Then, manage your interceptor lifecycle:
Using a local interceptor
// https://bit.ly/zimic-guides-testing beforeAll(async () => { // Start intercepting requests: // https://bit.ly/zimic-interceptor-http#http-interceptorstart await myInterceptor.start(); }); beforeEach(() => { // Clear interceptors so that no tests affect each other: // https://bit.ly/zimic-interceptor-http#http-interceptorclear myInterceptor.clear(); }); afterEach(() => { // Check that all expected requests were made: // https://bit.ly/zimic-interceptor-http#http-interceptorchecktimes myInterceptor.checkTimes(); }); afterAll(async () => { // Stop intercepting requests: // https://bit.ly/zimic-interceptor-http#http-interceptorstop await myInterceptor.stop(); });
Using a remote interceptor
// https://bit.ly/zimic-guides-testing beforeAll(async () => { // Start intercepting requests: // https://bit.ly/zimic-interceptor-http#http-interceptorstart await myInterceptor.start(); }); beforeEach(() => { // Clear interceptors so that no tests affect each other: // https://bit.ly/zimic-interceptor-http#http-interceptorclear await myInterceptor.clear(); }); afterEach(() => { // Check that all expected requests were made: // https://bit.ly/zimic-interceptor-http#http-interceptorchecktimes await myInterceptor.checkTimes(); }); afterAll(async () => { // Stop intercepting requests: // https://bit.ly/zimic-interceptor-http#http-interceptorstop await myInterceptor.stop(); });
If you are creating a remote interceptor, it's necessary to have a running interceptor server before starting it. The base URL of the remote interceptor should point to the server, optionally including a path to differentiate from other interceptors.
-
Now, you can intercept requests and return mock responses!
Using a local interceptor
test('example', async () => { const users: User[] = [{ username: 'my-user' }]; // Declare your mocks: // https://bit.ly/zimic-interceptor-http#http-interceptormethodpath const myHandler = myInterceptor .get('/users') // Use restrictions to make declarative assertions and narrow down your mocks: // https://bit.ly/zimic-interceptor-http#http-handlerwithrestriction .with({ headers: { authorization: 'Bearer my-token' }, searchParams: { username: 'my' }, }) // Respond with your mock data: // https://bit.ly/zimic-interceptor-http#http-handlerresponddeclaration .respond({ status: 200, body: users, }) // Define how many requests you expect your application to make: // https://bit.ly/zimic-interceptor-http#http-handlertimes .times(1); // Run your application and make requests: // ... // Check the requests you expect: // https://bit.ly/zimic-interceptor-http#http-handlerrequests // // NOTE: The code below checks the requests manually. This is optional in this // example because the `with` and `times` calls act as a declarative validation, // meaning that exactly one request is expected with specific data. If fewer or // more requests are received, the test will fail when `myInterceptor.checkTimes()` // is called in the `afterEach` hook. const requests = myHandler.requests(); expect(requests).toHaveLength(1); expect(requests[0].headers.get('authorization')).toBe('Bearer my-token'); expect(requests[0].searchParams.size).toBe(1); expect(requests[0].searchParams.get('username')).toBe('my'); expect(requests[0].body).toBe(null); });
Using a remote interceptor
test('example', async () => { const users: User[] = [{ username: 'my-user' }]; // Declare your mocks: // https://bit.ly/zimic-interceptor-http#http-interceptormethodpath const myHandler = await myInterceptor .get('/users') // Use restrictions to make declarative assertions and narrow down your mocks: // https://bit.ly/zimic-interceptor-http#http-handlerwithrestriction .with({ headers: { authorization: 'Bearer my-token' }, searchParams: { username: 'my' }, }) // Respond with your mock data: // https://bit.ly/zimic-interceptor-http#http-handlerresponddeclaration .respond({ status: 200, body: users, }) // Define how many requests you expect your application to make: // https://bit.ly/zimic-interceptor-http#http-handlertimes .times(1); // Run your application and make requests: // ... // Check the requests you expect: // https://bit.ly/zimic-interceptor-http#http-handlerrequests // // NOTE: The code below checks the requests manually. This is optional in this // example because the `with` and `times` calls act as a declarative validation, // meaning that exactly one request is expected with specific data. If fewer or // more requests are received, the test will fail when `myInterceptor.checkTimes()` // is called in the `afterEach` hook. const requests = await myHandler.requests(); expect(requests).toHaveLength(1); expect(requests[0].headers.get('authorization')).toBe('Bearer my-token'); expect(requests[0].searchParams.size).toBe(1); expect(requests[0].searchParams.get('username')).toBe('my'); expect(requests[0].body).toBe(null); });
-
Take a look at our examples and testing guide.
-
Check out the API reference:
-
Explore the
zimic
CLI:
© Zimic
npm |
Docs | Issues |
Examples | Roadmap
Help us improve these docs!
Report an issue or
edit on GitHub.