0.6.0 • Published 5 months ago

@betheweb/mockme v0.6.0

Weekly downloads
-
License
MIT
Repository
-
Last release
5 months ago

mockme

A mock service worker generator to intercept fetch calls in the browser and return mocks defined in custom files.

This package will add a CLI to allow the creation of a service worker implementation that uses the result of parsing any kind of mock file. This will be done using a plugin system, where each plugin should be able to create the mocks needed for a specific style of mocking.

Why mockme ?

There are some environments where you want to resolve your network (mainly API) calls, but you cannot reach the services or servers behind the scenes. Perhaps you need to benchmark the performance of your front-end solution, but you don't want the results to be affected by network response times, or you want to simulate what happens if a call takes 3 seconds to complete.

There are many scenarios where mocking responses using delays or different responses for different scenarios is a great tool to ensure you cover all edge cases. This is where mockme can help you.

How to install

Install with NPM

$ npm i -D @betheweb/mockme

Install with Bun

$ bun add -D @betheweb/mockme

Install with PNPM

$ pnpm add -D @betheweb/mockme

Configuration

The config file mockme.config.mjs should be placed in the root of your project. Here is an example:

import mockmeJsPlugin from '@betheweb/mockme-plugin-js';

export default {
  output: 'demo/service-worker.js',
  plugins: [
    mockmeJsPlugin({
      // plugin config
    }),
  ],
};

How to use it

In order to generate the service worker file from the command line, add the next script in your package.json file

{
  "scripts": {
    "mocks:sw": "mockme"
  }
}

If the config file is not in the root or you named it differently, just use the -c, --config option.

{
  "scripts": {
    "mocks:sw": "mockme --config path/to/my/mockmeconfig.mjs"
  }
}

Once the service worker file is generated, it is time to use it in the code. In order to help in this task, mockme provides a manager to wire up all at once so you don't need to do it by yourself. Just import the ServiceWorkerManager and call the register function with the path where the service provider was generated.

<html lang="en">
  <head>
    <title>Mockme example</title>
    <script type="module">
      import { ServiceWorkerManager } from '@betheweb/mockme/ServiceWorkerManager.js';

      ServiceWorkerManager.register('./sw.js');
    </script>
  </head>
</html>

Plugins

A mockme plugin is an object with name property, and a handler function to generate the output as described below, and which follows our conventions. A plugin should be distributed as a package that exports a function that can be called with plugin-specific options and returns such an object.

Conventions

  • Plugins should have type module.
  • Plugins should have a clear name with mockme-plugin- prefix.
  • Include mockme-plugin keyword in package.json.
  • Plugins should be tested. We recommend mocha or vitest.
  • Use asynchronous methods when it is possible, e.g. fs.readFile instead of fs.readFileSync.
  • Document your plugin in English.

Properties

name : string

The name of the plugin to be used when logging.

handler : function(logger?: Logger): Promise<MockSchema[]>|MockSchema[]

The function which is going to use the config to generate the output.

export function plugin(config) {
  return {
    name: 'mockme-plugin-test',
    handler: () => [], // Returns an array of objects that have a mock schema
  };
}

Mock Schema

All plugins should return an array of objects that should be validated using the Mock Schema. This is the definition for the schema:

type Request = {
  method: 'GET' | 'POST' | 'PUT' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'CONNECT';
  path: string;
  body?: object | string;
  queryParams?: Record<string, string>;
  headers?: Record<string, string>;
  cookies?: Record<string, string>;
};

type Response = {
  body?: object | string;
  headers?: Record<string, string>;
  status: number;
};

type mockSchema = {
  request: Request;
  response: Response | (() => Response);
  scenario?: string;
  delay?: number;
};

request.method

HTTP Verb used in the request. It is required and must have a value of GET,POST,PUT,HEAD,DELETE,OPTIONS or CONNECT.

request.path

Pathname of the request. It is required accepts segments like express routes.

Example:

{
  "request": {
    "method": "GET",
    "path": "/api/v1/books/:id"
  }
}

request.body

The body of the HTTP request. It is optional and should match

Example:

{
  "request": {
    "method": "POST",
    "path": "/api/v1/books",
    "body": {
      "title": "Harry Potter"
    }
  }
}

request.cookies

This object defines the conditions to match against the cookies included in the request. It is optional.

Example:

{
  "request": {
    "method": "GET",
    "path": "/api/v1/books/:id",
    "cookies": {
      "user": "1"
    }
  }
}

request.headers

This object defines the conditions to match against the headers included in the request. It is optional.

Example:

{
  "request": {
    "method": "GET",
    "path": "/api/v1/books/:id",
    "headers": {
      "Content-Type": "application/json"
    }
  }
}

request.queryParams

This object defines the conditions to match against url query parameters. It is optional.

Example:

{
  "request": {
    "method": "GET",
    "path": "/api/v1/books?page=1",
    "queryParams": {
      "page": "1"
    }
  }
}

All conditions are checked against the request and should pass. If there is a mismatch, no mock will be returned and the request will be passed to the network.

Here is a complex example where all conditions are combined:

{
  "request": {
    "method": "PUT",
    "path": "/api/v1/books/:id?pages=100",
    "queryParams": { "pages": "100" },
    "header": { "Authorization": "Bearer abcd" },
    "cookie": { "token": "12345" }
  }
}

To the service worker to match a request and return a mock data for it, the request should be like this:

const myHeaders = new Headers();
myHeaders.append('Content-Type', 'application/json');
myHeaders.append('Authorization', 'Bearer abcd');
myHeaders.append('Cookie', 'token=12345');

const requestOptions = {
  method: 'GET',
  headers: myHeaders,
  body: JSON.stringify({
    role: 'admin',
    title: 'Harry Potter',
  }),
};

async function updateBook() {
  try {
    const response = await fetch('https://test.com/api/v1/books/1?pages=100', requestOptions);
    const result = await response.json();
  } catch (error) {
    console.log(error);
  }
}

response

The response can be either a function or an object. In case you need to perform any logic before returning the response, you may use a function which will receive an object with path, queryParams, pathParams, body, headers and cookies keys from the request.

Example:

{
  response: ({ path, pathParams, queryParams, body, headers, cookies }) => {
    if (pathParams.id === '1') {
      return {
        body: {
          message: 'Book updated',
        },
        status: 200,
        delay: 3000,
      };
    } else {
      return {
        body: { message: 'Book not found' },
        status: 404,
      };
    }
  };
}

response.body

The body to include in the response. This is optional and it is set to an empty object if not present.

Example:

{
  "response": {
    "body": {
      "title": "Harry Potter",
      "id": "1"
    }
  }
}

response.headers

The headers to include in the response. This is optional.

{
  "response": {
    "headers": {
      "Content-Type": "application/json"
    }
  }
}

response.status

The status of the response. This is optional and default value is 200.

{
  "response": {
    "status": 404
  }
}

delay

This will set the response to be delayed by the number of milliseconds specified. This is optional and default value is 0. If the response is set as a function and it returns a value for delay, it will take precendence over this one.

scenario

The scenario the mock is going to be in. This is optional. When using the service worker generated with mockme, the scenario can be set so we can have multiple mocks for the same endpoint but for different scenarios.

Example :

[
  {
    "request": {
      "method": "GET",
      "path": "/api/v1/books"
    },
    "response": {
      "body": [{ "id": "1", "title": "Harry Potter" }]
    }
  },
  {
    "request": {
      "method": "GET",
      "path": "/api/v1/books"
    },
    "response": {
      "body": [
        { "id": "1", "title": "Harry Potter: Philosopher's stone" },
        { "id": "2", "title": "Harry Potter: Chamber of secrets" },
        { "id": "3", "title": "Harry Potter: Prisoner of Azkaban" }
      ]
    },
    "scenario": "3 books"
  }
]

If no scenario is set, the response will include one item, but if the scenario is set to '3 books', the response will include 3 items in the body.

0.6.0

5 months ago

0.5.2

5 months ago

0.5.1

5 months ago

0.5.0

5 months ago

0.4.2

6 months ago

0.4.1

6 months ago

0.4.0

6 months ago

0.3.1

6 months ago

0.3.0

6 months ago

0.2.1

7 months ago

0.2.0

7 months ago

0.1.0

7 months ago

0.0.1

8 months ago