0.1.1 • Published 4 months ago

request-mocking-protocol v0.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

Request Mocking Protocol (RMP)

A protocol for declarative mocking of HTTP requests. Useful in E2E testing when you need to mock API calls made on the server (e.g., in React Server Components).

Index

How it works

flowchart LR;
    A(Test Runner) -- "x-mock-request:<br><code style='font-size: 0.8em;padding: 0 10px'>{url:#quot;http:\/\/external/api#quot;,body:#quot;Hello#quot;}</code></span>" --> B(App Server);
    B -- &lt;h1&gt;Hello&lt;/h1&gt; --> A;
    B -. Mocked! .-> C(External API);
    C -.-> B;
  1. The mock defines a request and response in a serializable JSON format.
  2. When opening a webpage, the mock is attached to the navigation request as a custom HTTP header.
  3. On the server, an interceptor reads the mock header and applies mock to the outbound API requests.
  4. The application server renders the page using data from the mocked response.

Concepts

Request Schema

The request schema is a serializable object that defines parameters for matching a request.

Example:

{
  method: 'GET', 
  url: 'https://jsonplaceholder.typicode.com/users',
  query: {
    foo: 'bar'
  }
}

This schema will match the request:

GET https://jsonplaceholder.typicode.com/users?foo=bar

Full schema definition.

Response Schema

The response schema is a serializable object that defines how to build the mocked response.

Example:

{
  status: 200,
  body: 'Hello world'
}

Full schema definition.

Transport

Transport allows passing mock schemas from the test runner to the application server. RMP uses a custom HTTP header x-mock-request for transferring a JSON-stringified schemas list.

Example:

x-mock-request: [{"reqSchema":{"method":"GET","patternType":"urlpattern","url":"https://example.com"},"resSchema":{"body":"hello","status":200}}]

On the server side, the interceptor will read inbound headers and apply mocks.

Installation

npm i -D request-mocking-protocol

Usage

To mock requests with RMP, you need to perform two actions:

  1. Setup a request interceptor in the application.
  2. Define mocks in your test using the MockClient class.

Check out the sections below for integration with varios frameworks and test-runners.

App Integration

Next.js

Add the following code to the instrumentation.ts file:

import { headers } from 'next/headers';

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs' && process.env.NODE_ENV !== 'production') {
    const { setupFetchInterceptor } = await import('request-mocking-protocol/fetch');
    setupFetchInterceptor(() => headers());
  }
}

Astro

See astro.config.ts in the astro-cypress example.

Test-runners Integration

On the test runner side, you can define request mocks via the MockClient class.

Playwright

Set up the mockServerRequest fixture:

import { test as base } from '@playwright/test';
import { MockClient } from 'request-mocking-protocol';

export const test = base.extend<{ mockServerRequest: MockClient }>({
  mockServerRequest: async ({ context }, use) => {
    const mockClient = new MockClient();
    mockClient.onChange = async (headers) => context.setExtraHTTPHeaders(headers);
    await use(mockClient);
  },
});

Use the mockServerRequest fixture to define server-side request mocks:

test('my test', async ({ page, mockServerRequest }) => {
  await mockServerRequest.GET('https://jsonplaceholder.typicode.com/users', {
    body: [{ id: 1, name: 'John Smith' }],
  });

  // ...
});

Cypress

  1. Add a custom command mockServerRequest in support files, see example mock-server-request.js.

  2. Use the custom command to define mocks:

    it('shows list of users', () => {
      cy.mockServerRequest('https://jsonplaceholder.typicode.com/users', {
        body: [{ id: 1, name: 'John Smith' }],
      });
    
      // ...
    });

API

Interceptors

Interceptors are used on the server side to capture HTTP requests and apply mocks. Currently, there are two interceptors available.

Fetch

This interceptor hooks into globalThis.fetch.

Basic usage:

const { setupFetchInterceptor } = await import('request-mocking-protocol/fetch');

setupFetchInterceptor(() => { /* retrieve headers of the inbound HTTP request */ });

The actual function for retrieving inbound headers depends on the application framework.

MSW

You can use MSW to intercept server-side requests:

import { setupServer } from 'msw/node';
import { createHandler } from 'request-mocking-protocol/msw';

const mockHandler = createHandler(() => { /* retrieve headers of the inbound HTTP request */ });
const mswServer = setupServer(mockHandler);
mswServer.listen();

The actual function for retrieving inbound headers depends on the application framework.

MockClient

The MockClient class is used on the test-runner side to define HTTP request mocks.

Constructor

constructor(options?: MockClientOptions)

Creates a new instance of MockClient.

  • options (optional): An object containing configuration options.
    • debug (optional): A boolean indicating whether to enable debug mode.
    • defaultMethod (optional): The default HTTP method to use for requests.

Properties

headers: Record<string, string>

Returns HTTP headers that are built from the mock schemas. Can be sent to the server for mocking server-side requests.

onChange?: (headers: Record<string, string>) => unknown

A callback function that is called whenever the mocks are changed.

Methods

async addMock(reqSchema, resSchema): Promise<void>
async GET(reqSchema, resSchema): Promise<void>
async POST(reqSchema, resSchema): Promise<void>
async PUT(reqSchema, resSchema): Promise<void>
async DELETE(reqSchema, resSchema): Promise<void>
async HEAD(reqSchema, resSchema): Promise<void>
async ALL(reqSchema, resSchema): Promise<void>

Adds a new mock for the corresponding HTTP method.

  • reqSchema: The request schema to add.
  • resSchema: The response schema to add.
async reset(): Promise<void>

Clears all mock schemas and rebuilds the headers.

License

MIT

0.1.1

4 months ago

0.1.0

4 months ago