Skip to content

getting‐started

github-actions[bot] edited this page Oct 6, 2024 · 8 revisions

Getting Started

Contents


1. Requirements

Supported environments

Supported languages

  • TypeScript >= 4.7
  • 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,
  },
}

2. Installation

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

3. Post-install

Client-side post-install

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!

Server-side post-install

No additional configuration is required for server-side applications!

4. Choose your method to intercept requests

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.

Local HTTP 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.

When to use local HTTP 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.

Remote HTTP interceptors

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.

When to use remote HTTP 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.

5. Create your first interceptor

  1. 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 and GET requests to /users. A successful response after creating a user is a User object, whereas listing users returns an array of User objects. Errors are represented by a RequestError object.

    You can also use zimic typegen to automatically generate types for your interceptor schema.

  2. 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();
    });
    
    afterEach(() => {
      // Clear interceptors so that no tests affect each other
      // https://bit.ly/zimic-interceptor-http#http-interceptorclear
      myInterceptor.clear();
    });
    
    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();
    });
    
    afterEach(() => {
      // Clear interceptors so that no tests affect each other
      // https://bit.ly/zimic-interceptor-http#http-interceptorclear
      await myInterceptor.clear();
    });
    
    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.

  3. Now, you can intercept requests and return mock responses!

    Using a local interceptor
    test('example', async () => {
      const users: User[] = [{ username: 'diego-aquino' }];
    
      // 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: 'diego' },
        })
        // Respond with your mock data
        // https://bit.ly/zimic-interceptor-http#http-handlerresponddeclaration
        .respond({
          status: 200,
          body: users,
        });
    
      // Run your application and make requests
      // ...
    
      // Check the requests you expect
      // https://bit.ly/zimic-interceptor-http#http-handlerrequests
      const requests = myHandler.requests();
      expect(requests).toHaveLength(1);
    
      // The following expects are automatically checked by the restrictions
      // we declared above. Requests not matching them will cause warnings and not
      // be intercepted.
    
      // If you are not using restrictions, asserting the requests manually is
      // a good practice:
      expect(requests[0].headers.get('authorization')).toBe('Bearer my-token');
    
      expect(requests[0].searchParams.size).toBe(1);
      expect(requests[0].searchParams.get('username')).toBe('diego');
    
      expect(requests[0].body).toBe(null);
    });
    Using a remote interceptor
    test('example', async () => {
      const users: User[] = [{ username: 'diego-aquino' }];
    
      // 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: 'diego' },
        })
        // Respond with your mock data
        // https://bit.ly/zimic-interceptor-http#http-handlerresponddeclaration
        .respond({
          status: 200,
          body: users,
        });
    
      // Run your application and make requests
      // ...
    
      // Check the requests you expect
      // https://bit.ly/zimic-interceptor-http#http-handlerrequests
      const requests = await myHandler.requests();
      expect(requests).toHaveLength(1);
    
      // The following expects are automatically checked by the restrictions
      // we declared above. Requests not matching them will cause warnings and not
      // be intercepted.
    
      // If you are not using restrictions, asserting the requests manually is
      // a good practice:
      expect(requests[0].headers.get('authorization')).toBe('Bearer my-token');
    
      expect(requests[0].searchParams.size).toBe(1);
      expect(requests[0].searchParams.get('username')).toBe('diego');
    
      expect(requests[0].body).toBe(null);
    });

6. Next steps

Clone this wiki locally