Skip to content

Commit

Permalink
feat: add disable and enable method to Mockiavelli class (#9)
Browse files Browse the repository at this point in the history
resolve #9
  • Loading branch information
Fiszcz committed Apr 7, 2021
1 parent d50eb6d commit 875cd29
Show file tree
Hide file tree
Showing 11 changed files with 16,450 additions and 120 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Main features
- [Matching priority](#matching-priority)
- [Specifying API base url](#base-url)
- [Cross-origin (cross-domain) API requests](#cors)
- [Stop mocking](#stop-mocking)
- [Dynamic responses](#dynamic-responses)
- [Not matched requests](#not-matched-requests)
- [Debug mode](#debug-mode)
Expand Down Expand Up @@ -304,6 +305,28 @@ mockiavelli.mockGET('http://api.example.com/api/users', { status: 200 });
// GET http://another-domain.example.com/api/users => 404
```

### Stop mocking <a name="stop-mocking">

To stop intercept requests you can call `mockiavelli.disable` method (all requests will send to real services).
Then you can enable mocking again by `mockiavelli.enable` method.

```typescript
mockiavelli.mockGET('/api/users/:userId', {
status: 200,
body: { name: 'John Doe' },
});

// GET /api/users/1234 => 200 { name: 'John Doe' }

mockiavelli.disable();

// GET /api/users/1234 => 200 { name: 'Jacob Kowalski' } <- real data from backend

mockiavelli.enable();

// GET /api/users/1234 => 200 { name: 'John Doe' }
```

### Dynamic responses <a name="dynamic-responses"/>

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:
Expand Down Expand Up @@ -475,6 +498,16 @@ mockiavelli.mockGET('http://example.com/api/clients/', {
});
```

#### `mockiavelli.disable()`

Stop mocking of requests by Mockiavelli.
After that all requests pass to real endpoints.
This method does not reset set mocks or possibility to set mocks, so when we then enable again mocking by `mockiavelli.enable()`, all set mocks works again.

#### `mockiavelli.enable()`

To enable mocking of requests by Mockiavelli when previously `mockiavelli.diable()` was called.

---

### `class Mock` <a name="Mock"/>
Expand Down
16,374 changes: 16,310 additions & 64 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"husky": "^1.3.1",
"jest": "^24.5.0",
"jest-junit": "^6.3.0",
"playwright-chromium": "^1.0.1",
"playwright-chromium": "1.0.1",
"prettier": "^2.0.5",
"pretty-quick": "^1.10.0",
"puppeteer": "2.1.0",
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/BrowserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { QueryObject } from '../types';
* Interface used by Mockiavelli to communicate with browser automation libraries
*/
export interface BrowserController {
startInterception(onRequest: BrowserRequestHandler): Promise<void>;
startInterception(): Promise<void>;
stopInterception(): Promise<void>;
}

/**
Expand Down
13 changes: 8 additions & 5 deletions src/controllers/BrowserControllerFactory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as puppeteer from 'puppeteer';
import playwright from 'playwright-core';
import playwright from 'playwright-chromium';
import { PlaywrightController } from './PlaywrightController';
import { PuppeteerController } from './PuppeteerController';
import { BrowserController } from './BrowserController';
import { BrowserController, BrowserRequestHandler } from './BrowserController';

/**
* Type of supported page objects
Expand All @@ -13,11 +13,14 @@ export class BrowserControllerFactory {
/**
* Returns instance of BrowserController corresponding to provided page
*/
public static createForPage(page: BrowserPage): BrowserController {
public static createForPage(
page: BrowserPage,
onRequest: BrowserRequestHandler
): BrowserController {
if (this.isPlaywrightPage(page)) {
return new PlaywrightController(page);
return new PlaywrightController(page, onRequest);
} else if (this.isPuppeteerPage(page)) {
return new PuppeteerController(page);
return new PuppeteerController(page, onRequest);
} else {
throw new Error(
'Expected instance of Puppeteer or Playwright Page. Got: ' +
Expand Down
39 changes: 24 additions & 15 deletions src/controllers/PlaywrightController.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
import playwright from 'playwright-core';
import playwright from 'playwright-chromium';
import { parse } from 'url';
import { getOrigin, tryJsonParse } from '../utils';
import {
BrowserController,
BrowserRequestHandler,
ResponseData,
BrowserRequest,
BrowserRequestHandler,
BrowserRequestType,
ResponseData,
} from './BrowserController';
import { getOrigin, tryJsonParse } from '../utils';
import { parse } from 'url';

export class PlaywrightController implements BrowserController {
constructor(private readonly page: playwright.Page) {}

async startInterception(onRequest: BrowserRequestHandler) {
this.page.route('**/*', (route: playwright.Route) => {
onRequest(
this.toBrowserRequest(route.request()),
(data) => this.respond(route, data),
() => this.skip(route)
);
});
constructor(
private readonly page: playwright.Page,
private readonly onRequest: BrowserRequestHandler
) {}

public async startInterception() {
await this.page.route('**/*', this.requestHandler);
}

public async stopInterception() {
await this.page.unroute('**/*', this.requestHandler);
}

private requestHandler = (route: playwright.Route) => {
this.onRequest(
this.toBrowserRequest(route.request()),
(data) => this.respond(route, data),
() => this.skip(route)
);
};

private toBrowserRequest(request: playwright.Request): BrowserRequest {
// TODO find a better alternative for url.parse
const { pathname, query, protocol, host } = parse(request.url(), true);
Expand Down
28 changes: 19 additions & 9 deletions src/controllers/PuppeteerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,29 @@ import {
import { getOrigin, tryJsonParse } from '../utils';

export class PuppeteerController implements BrowserController {
constructor(private readonly page: Page) {}
constructor(
private readonly page: Page,
private readonly onRequest: BrowserRequestHandler
) {}

async startInterception(onRequest: BrowserRequestHandler) {
public async startInterception() {
await this.page.setRequestInterception(true);
this.page.on('request', (request) => {
onRequest(
this.toBrowserRequest(request),
(response) => request.respond(response),
() => request.continue()
);
});
await this.page.on('request', this.requestHandler);
}

public async stopInterception() {
await this.page.setRequestInterception(false);
await this.page.off('request', this.requestHandler);
}

private requestHandler = (request: Request) => {
this.onRequest(
this.toBrowserRequest(request),
(response) => request.respond(response),
() => request.continue()
);
};

private toBrowserRequest(request: Request): BrowserRequest {
// TODO find a better alternative for url.parse
const { pathname, query, protocol, host } = parse(request.url(), true);
Expand Down
43 changes: 26 additions & 17 deletions src/mockiavelli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import dbg from 'debug';
import {
BrowserController,
BrowserRequestHandler,
BrowserRequestType,
} from './controllers/BrowserController';
import {
BrowserControllerFactory,
BrowserPage,
} from './controllers/BrowserControllerFactory';
import { Mock } from './mock';
import {
MockedResponse,
Expand All @@ -8,21 +17,12 @@ import {
} from './types';
import {
addMockByPriority,
printRequest,
createRequestMatcher,
getCorsHeaders,
sanitizeHeaders,
printRequest,
printResponse,
sanitizeHeaders,
} from './utils';
import {
BrowserController,
BrowserRequestHandler,
BrowserRequestType,
} from './controllers/BrowserController';
import {
BrowserPage,
BrowserControllerFactory,
} from './controllers/BrowserControllerFactory';

const debug = dbg('mockiavelli:main');

Expand All @@ -36,8 +36,14 @@ export interface MockiavelliOptions {
export class Mockiavelli {
private readonly baseUrl: string = '';
private mocks: Mock[] = [];
private controller: BrowserController;

constructor(page: BrowserPage, options: Partial<MockiavelliOptions> = {}) {
this.controller = BrowserControllerFactory.createForPage(
page,
this.onRequest
);

constructor(options: Partial<MockiavelliOptions> = {}) {
if (options.baseUrl) {
this.baseUrl = options.baseUrl;
}
Expand All @@ -52,14 +58,17 @@ export class Mockiavelli {
page: BrowserPage,
options: Partial<MockiavelliOptions> = {}
): Promise<Mockiavelli> {
const instance = new Mockiavelli(options);
const controller = BrowserControllerFactory.createForPage(page);
await instance.activate(controller);
const instance = new Mockiavelli(page, options);
await instance.enable();
return instance;
}

private async activate(controller: BrowserController): Promise<void> {
await controller.startInterception(this.onRequest);
public async enable(): Promise<void> {
await this.controller.startInterception();
}

public async disable(): Promise<void> {
await this.controller.stopInterception();
}

public mock<TResponseBody = any>(
Expand Down
16 changes: 16 additions & 0 deletions test/integration/mockiavelli.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,4 +706,20 @@ describe.each(CONTROLLERS)(`Mockiavelli integration (%s)`, (controller) => {
});
await mock.waitForRequestsCount(2);
});

test('disable() method disables mocking of requests', async () => {
const mockedFun = jest.fn().mockReturnValue({ status: 200 });
await ctx.mockiavelli.mockGET('/example', mockedFun);

const response1 = await ctx.makeRequest('GET', '/example');
expect(response1.status).toEqual(200);

await ctx.mockiavelli.disable();
const response2 = await ctx.makeRequest('GET', '/example');
expect(response2.status).toEqual(404);

await ctx.mockiavelli.enable();
const response3 = await ctx.makeRequest('GET', '/example');
expect(response3.status).toEqual(200);
});
});
7 changes: 5 additions & 2 deletions test/unit/mockiavelli.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Mockiavelli, Mock } from '../../src';
import { BrowserPage } from '../../src/controllers/BrowserControllerFactory';
import { createMockPage } from './fixtures/page';
jest.mock('../../src/mock');
jest.mock('../../src/controllers/BrowserControllerFactory', () => ({
Expand Down Expand Up @@ -32,12 +33,14 @@ describe('Mockiavelli', () => {
const mockResponse = { status: 404, body: {} };

beforeEach(async () => {
mockiavelli = new Mockiavelli();
mockiavelli = new Mockiavelli({} as BrowserPage);
});

describe('mock()', () => {
test('should add API base url to request matcher', () => {
const mockiavelli = new Mockiavelli({ baseUrl: '/api/foo' });
const mockiavelli = new Mockiavelli({} as BrowserPage, {
baseUrl: '/api/foo',
});
mockiavelli.mock({ url: '/boo', method: 'GET' }, mockResponse);
expect(Mock).toHaveBeenCalledWith(
{ url: '/api/foo/boo', method: 'GET' },
Expand Down
12 changes: 6 additions & 6 deletions test/unit/puppeteerController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { PuppeteerRequestMock } from './fixtures/PuppeteerRequest';
describe('PuppeteerAdapter', () => {
test('.start() subscribes for page request event', async () => {
const page = createMockPage();
const adapter = new PuppeteerController(page);
await adapter.startInterception(() => {});
const adapter = new PuppeteerController(page, () => {});
await adapter.startInterception();
expect(page.setRequestInterception).toHaveBeenCalledWith(true);
expect(page.on).toHaveBeenCalledWith('request', expect.any(Function));
});

test('returns serialized request object', async () => {
const page = createMockPage();
const adapter = new PuppeteerController(page);
const handler = jest.fn();
await adapter.startInterception(handler);
const adapter = new PuppeteerController(page, handler);
await adapter.startInterception();

// Trigger request
page.on.mock.calls[0][1](
Expand Down Expand Up @@ -53,9 +53,9 @@ describe('PuppeteerAdapter', () => {

test('returns correct path and url when origin contains trailing slash', async () => {
const page = createMockPage();
const adapter = new PuppeteerController(page);
const handler = jest.fn();
await adapter.startInterception(handler);
const adapter = new PuppeteerController(page, handler);
await adapter.startInterception();

// Trigger request
page.on.mock.calls[0][1](
Expand Down

0 comments on commit 875cd29

Please sign in to comment.