@betheweb/mockme
v0.6.0
Published
A mock service worker generator
Downloads
32
Readme
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 inpackage.json
. - Plugins should be tested. We recommend mocha or vitest.
- Use asynchronous methods when it is possible, e.g.
fs.readFile
instead offs.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.