Mockiavelli is HTTP request mocking library for Puppeteer and Playwright. It was created to enable effective testing of Single Page Apps in isolation and independently from API services.
Main features
- simple, minimal API
- mock network requests directly in the test case
- inspect and assert requests payload
- match request by method, url, path parameters and query strings
- support for cross-origin requests
- works with every testing framework running in node.js
- fully typed in Typescript and well tested
- lightweight - only 4 total dependencies (direct and indirect)
npm install mockiavelli -D
or if you are using yarn:
yarn add mockiavelli -D
- Mockiavelli requires one of the following to be installed separately:
- Puppeteer (in versions 2.x - 8.x)
- Playwright (in version 1.x)
- If you're using jest we also recommend to install jest-puppeteer or jest-playwright
To start using Mockiavelli, you need to instantiate it by providing it a page
- instance of Puppeteer Page or Playwright Page
import { Mockiavelli } from 'mockiavelli';
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const mockiavelli = await Mockiavelli.setup(page);
Mockiavelli will start to intercept all HTTP requests issued from this page.
To define response for a given request, call mockiavelli.mock<HTTP_METHOD>
with request URL and response object:
const getUsersMock = mockiavelli.mockGET('/api/users', {
status: 200,
body: [
{ id: 123, name: 'John Doe' },
{ id: 456, name: 'Mary Jane' },
],
});
Now every GET /api/users
request issued from this page will receive 200 OK
response with provided body.
const users = await page.evaluate(() => {
return fetch('/api/users').then((res) => res.json());
});
console.log(users); // [{id: 123, name: 'John Doe' }, {id: 456, name: 'Mary Jane'}]
The example below is a Jest test case (with jest-puppeteer preset) verifies a sign-up form in a locally running application.
Mockiavelli is used to mock and assert request that the app makes to REST API upon form submission.
import { Mockiavelli } from 'mockiavelli';
test('Sign-up form', async () => {
// Enable mocking on instance of puppeteer Page (provided by jest-puppeteer)
const mockiavelli = await Mockiavelli.setup(page);
// Navigate to application
await page.goto('http://localhost:8000/');
// Configure mocked response
const postUserMock = mockiavelli.mockPOST('/api/user', {
status: 201,
body: {
userId: '123',
},
});
// Perform interaction
await page.type('input.name', 'John Doe');
await page.type('input.email', 'email@example.com');
await page.click('button.submit');
// Verify request payload
const postUserRequest = await postUserMock.waitForRequest();
expect(postUserRequest.body).toEqual({
user_name: 'John Doe',
user_email: 'email@example.com',
});
// Verify message shown on the screen
await expect(page).toMatch('Created account ID: 123');
});
Request can be matched by:
-
providing URL string to
mockiavelli.mock<HTTP_METHOD>
method:mockiavelli.mockGET('/api/users?age=30', {status: 200, body: [....]})
-
providing matcher object to
mockiavelli.mock<HTTP_METHOD>
methodmockiavelli.mockGET({ url: '/api/users', query: { age: '30' } }, {status: 200, body: [....]})
-
providing full matcher object
mockiavelli.mock
methodmockiavelli.mock({ method: 'GET', url: '/api/users', query: { age: '30' } }, {status: 200, body: [...]})
Path parameters in the URL can be matched using :param
notation, thanks to path-to-regexp library.
If mock matches the request, those params are exposed in request.params
property.
const getUserMock = mockiavelli.mockGET('/api/users/:userId', { status: 200 });
// GET /api/users/1234 => 200
// GET /api/users => 404
// GET /api/users/1234/categories => 404
console.log(await getUserMock.waitForRequest());
// { params: {userId : "12345"}, path: "/api/users/12345", ... }
Mockiavelli uses
Mockiavelli supports matching requests by query parameters. All defined params are then required to match the request, but excess params are ignored:
mockiavelli.mockGET('/api/users?city=Warsaw&sort=asc', { status: 200 });
// GET /api/users?city=Warsaw&sort=asc => 200
// GET /api/users?city=Warsaw&sort=asc&limit=10 => 200
// GET /api/users?city=Warsaw => 404
It is also possible to define query parameters as object. This notation works great for matching array query params:
mockiavelli.mockGET(
{ url: '/api/users', query: { status: ['active', 'blocked'] } },
{ status: 200 }
);
// GET /api/users?status=active&status=blocked => 200
mockiavelli.mock<HTTP_METHOD>
and mockiavelli.mock
methods return an instance of Mock
class that records all requests the matched given mock.
To assert details of request made by application use async mock.waitForRequest()
method. It will throw an error if no matching request was made.
const postUsersMock = mockiavelli.mockPOST('/api/users', { status: 200 });
// ... perform interaction on tested page ...
const postUserRequest = await postUsersMock.waitForRequest(); // Throws if POST /api/users request was not made
expect(postUserRequest.body).toBe({
name: 'John',
email: 'john@example.com',
});
By default mock are persistent, meaning that they will respond to multiple matching requests:
mockiavelli.mockGET('/api/users', { status: 200 });
// GET /api/users => 200
// GET /api/users => 200
To change this behaviour and disable mock once it matched a request use once
option:
mockiavelli.mockGET('/api/users', { status: 200 }, { once: true });
// GET /api/users => 200
// GET /api/users => 404
Mocks are matched in the "newest first" order. To override previously defined mock simply define new one:
mockiavelli.mockGET('/api/users', { status: 200 });
mockiavelli.mockGET('/api/users', { status: 401 });
// GET /api/users => 401
mockiavelli.mockGET('/api/users', { status: 500 });
// GET /api/users => 500
To change the default "newest first" matching order, you define mocks with combination of once
and priority
parameters:
mockiavelli.mockGET(
'/api/users',
{ status: 404 },
{ once: true, priority: 10 }
);
mockiavelli.mockGET('/api/users', { status: 500 }, { once: true, priority: 5 });
mockiavelli.mockGET('/api/users', { status: 200 });
// GET /api/users => 404
// GET /api/users => 500
// GET /api/users => 200
It is possible to initialize Mockiavelli instance with specified API base url. This API base url is added to every mocked request url.
const mockiavelli = await Mockiavelli.setup(page, { baseUrl: '/api/v1' });
mockiavelli.mockGET('/users', { status: 200 });
// GET /api/v1/users => 200
Mockiavelli has built-in support for cross-origin requests. If application and API are not on the same origin (domain) just provide the full request URL to mockiavelli.mock<HTTP_METHOD>
mockiavelli.mockGET('http://api.example.com/api/users', { status: 200 });
// GET http://api.example.com/api/users => 200
// GET http://another-domain.example.com/api/users => 404
It is possible to define mocked response in function of incoming request. This is useful if you need to use some information from request URL or body in the response:
mockiavelli.mockGET('/api/users/:userId', (request) => {
return {
status: 200,
body: {
id: request.params.userId,
name: 'John',
email: 'john@example.com',
...
},
};
});
// GET /api/users/123 => 200 {"id": "123", ... }
In usual scenarios, you should mock all requests done by your app.
Any XHR or fetched request done by the page not matched by any mock will be responded with 404 Not Found
. Mockiavelli will also log this event to console:
Mock not found for request: type=fetch method=GET url=http://example.com
Passing {debug: true}
to Mockiavelli.setup
enables rich debugging in console:
await Mockiavelli.setup(page, { debug: true });
Factory method used to set-up request mocking on provided Puppeteer or Playwright Page. It creates and returns an instance of Mockiavelli
Once created, mockiavelli will intercept all requests made by the page and match them with defined mocks.
If request does not match any mocks, it will be responded with 404 Not Found
.
page
(Page) instance of Puppeteer Page or Playwright Pageoptions
(object) configuration optionsbaseUrl: string
specify the API base url, which will be added to every mocked request urldebug: boolean
turns debug mode with logging to console (default:false
)
import { puppeteer } from 'puppeteer';
import { Mockiavelli } from 'mockiavelli';
const browser = await puppeteer.launch();
const page = await browser.newPage();
const mockiavelli = await Mockiavelli.setup(page);
Promise resolved with instance of Mockiavelli
once request mocking is established.
Respond all requests of matching matcher
with provided response
.
matcher
(object) matches request with mock.method: string
- any valid HTTP methodurl: string
- can be provided as path (/api/endpoint
) or full URL (http://example.com/endpoint
) for CORS requests. Supports path parameters (/api/users/:user_id
)query?: object
object literal which accepts strings and arrays of strings as values, transformed to queryString
response
(object | function) content of mocked response. Can be a object or a function returning object with following properties:status: number
headers?: object
body?: any
options?
(object) optional config objectprority
(number) when intercepted request matches multiple mock, mockiavelli will use the one with highest priorityonce
(boolean) (default: false) when set to true intercepted request will be matched only once
Instance of Mock
.
mockiavelli.mock(
{
method: 'GET',
url: '/api/clients',
query: {
city: 'Bristol',
limit: 10,
},
},
{
status: 200,
headers: {...},
body: [{...}],
}
);
Shorthand method for mockiavelli.mock
. Matches all request with HTTP_METHOD
method. In addition to matcher object, it also accepts URL string as first argument.
matcher: (string | object)
URL string or object with following properties:url: string
- can be provided as path (/api/endpoint
) or full URL (http://example.com/endpoint
) for CORS requests. Supports path parameters (/api/users/:user_id
)query?: object
object literal which accepts strings and arrays of strings as values, transformed to queryString
response: (object | function)
content of mocked response. Can be a object or a function returning object with following properties:status: number
headers?: object
body?: any
options?: object
optional config objectprority?: number
when intercepted request matches multiple mock, mockiavelli will use the one with highest priority. Default:0
once: boolean
when set to true intercepted request will be matched only once. Default:false
Available methods are:
mockiavelli.mockGET
mockiavelli.mockPOST
mockiavelli.mockDELETE
mockiavelli.mockPUT
mockiavelli.mockPATCH
// Basic example
mockiavelli.mockPOST('/api/clients', {
status: 201,
body: {...},
});
// Match by query parameters passed in URL
mockiavelli.mockGET('/api/clients?city=Bristol&limit=10', {
status: 200,
body: [{...}],
});
// Match by path params
mockiavelli.mockGET('/api/clients/:clientId', {
status: 200,
body: [{...}],
});
// CORS requests
mockiavelli.mockGET('http://example.com/api/clients/', {
status: 200,
body: [{...}],
});
Retrieve n-th request matched by the mock. The method is async - it will wait 100ms for requests to be intercepted to avoid race condition issue. Throws if mock was not matched by any request.
index
(number) index of request to return. Default: 0.
Promise resolved with MatchedRequest
- object representing request that matched the mock:
method: string
- request's method (GET, POST, etc.)url: string
- request's full URL. Example:http://example.com/api/clients?name=foo
hostname: string
- request protocol and host. Example:http://example.com
headers: object
- object with HTTP headers associated with the request. All header names are lower-case.path: string
- request's url path, without query string. Example:'/api/clients'
query: object
- request's query object, as returned fromquerystring.parse
. Example:{name: 'foo'}
body: any
- JSON deserialized request's post body, if anytype: string
- request's resource type. Possible values arexhr
andfetch
params: object
- object with path parameters specified inurl
const patchClientMock = mockiavelli.mockPATCH('/api/client/:clientId', { status: 200 });
// .. send request from page ...
const patchClientRequest = await patchClientMock.waitForRequest();
expect(patchClientRequest).toEqual({
method: 'PATCH',
url: 'http://example.com/api/client/1020',
hostname: 'http://example.com',
headers: {...},
path: '/api/client/1020',
query: {},
body: {name: 'John', email: 'john@example.com'}
rawBody: '{\"name\":\"John\",\"email\":\"john@example.com\"}',
type: 'fetch',
params: { clientId: '' }
})
Waits until mock is matched my n
requests. Throws error when timeout (equal to 100ms) is exceeded.