From 9699ef0ff2fede55bb5891b63fa1cde1ea87b12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Fajfer?= Date: Wed, 24 Jul 2019 09:55:30 +0200 Subject: [PATCH] feat: QA-14 Implemented matching most recently added mock first. BREAKING CHANGE: Changed order for adding mocks - newest first. --- README.md | 2 + src/mocketeer.ts | 10 +- src/rest-mock.ts | 6 +- src/utils.ts | 12 ++ test/integration/mocketeer.int.test.ts | 194 ++++++++++++++++++++----- test/unit/fixtures/request.ts | 21 +++ test/unit/http-mock.test.ts | 66 +-------- test/unit/utils.test.ts | 56 ++++++- 8 files changed, 258 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index 32f40c8..8f32c8d 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ await mocketeer.activate(page); #### .mockREST(filter: RequestFilter, response: MockedResponse, options?): RestMock Respond to xhr and fetch requests that match the `filter` with provided `response`. +Request are matched based on adding order - most recently added first. Pass query params through `query` argument in `filter` object or simply append text to `url` ###### Arguments @@ -220,6 +221,7 @@ mocketeer.mockREST( #### .mockDELETE(filter: RequestMethodFilter | string, response: MockedResponse, options?): RestMock Respond to xhr and fetch requests with adequate rest method that match the `filter` with provided `response`. +Request are matched based on adding order - most recently added first. Pass `filter` as an object or as an `url` string. Pass query params through `query` argument in `filter` object or simply append text to `url` diff --git a/src/mocketeer.ts b/src/mocketeer.ts index 9898e15..9e534f8 100644 --- a/src/mocketeer.ts +++ b/src/mocketeer.ts @@ -9,7 +9,7 @@ import { RequestMethodFilter, REST_METHOD, } from './types'; -import { printRequest, requestToPlainObject } from './utils'; +import { addMockByPriority, printRequest, requestToPlainObject } from './utils'; const interceptedTypes: ResourceType[] = ['xhr', 'fetch']; @@ -59,7 +59,8 @@ export class Mocketeer { const mock = new RestMock(filter, response, { ...options, }); - this.mocks.push(mock); + + addMockByPriority(this.mocks, mock); return mock; } @@ -71,7 +72,8 @@ export class Mocketeer { const mock = new RestMock(filter, response, { ...options, }); - this.mocks.push(mock); + + addMockByPriority(this.mocks, mock); return mock; } @@ -172,7 +174,7 @@ export class Mocketeer { const { protocol, host } = parse(originFrameUrl); const origin = `${protocol}//${host}`; - for (const mock of this.mocks.sort(RestMock.sortByPriority)) { + for (const mock of this.mocks) { const response = mock.getResponseForRequest(requestData, origin); if (response) { diff --git a/src/rest-mock.ts b/src/rest-mock.ts index bc762bc..5706434 100644 --- a/src/rest-mock.ts +++ b/src/rest-mock.ts @@ -24,7 +24,7 @@ export class RestMock implements IMock { private filter: ParsedFilterRequest; private response: MockedResponse; private requests: Array = []; - private options: MockOptions = { + public options: MockOptions = { priority: 0, once: false, }; @@ -182,10 +182,6 @@ export class RestMock implements IMock { }); } - public static sortByPriority(a: RestMock, b: RestMock) { - return b.options.priority - a.options.priority; - } - private prettyPrint(): string { const qs = stringify(this.filter.query); return `(${this.debugId}) ${this.filter.method} ${this.filter.path + diff --git a/src/utils.ts b/src/utils.ts index 98bcaed..a71d774 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -67,3 +67,15 @@ export function nth(d: number): string { return `${d}th`; } } + +export function addMockByPriority( + mockArr: T[], + mock: T +) { + const index = mockArr.findIndex( + (item: T) => item.options.priority <= mock.options.priority + ); + const calculatedIndex = index === -1 ? mockArr.length : index; + mockArr.splice(calculatedIndex, 0, mock); + return mockArr; +} diff --git a/test/integration/mocketeer.int.test.ts b/test/integration/mocketeer.int.test.ts index e2b76d3..d2073c7 100644 --- a/test/integration/mocketeer.int.test.ts +++ b/test/integration/mocketeer.int.test.ts @@ -467,47 +467,173 @@ describe('Mocketeer integration', () => { await expect(mock.getRequest()).resolves.toEqual(expect.anything()); }); - it('matches only once request with once set to true', async () => { - spyOn(console, 'error'); - await mocketeer.mockREST(requestGetFoo, response200Ok, { once: true }); + describe('ordering', () => { + const makeRequest = () => + page.evaluate(() => fetch('/foo').then(res => res.status)); - const response = await page.evaluate(() => - fetch('/foo').then(res => res.json()) - ); + it('matches only once request with once set to true', async () => { + spyOn(console, 'error'); + await mocketeer.mockREST(requestGetFoo, response200Ok, { + once: true, + }); - await expect(response).toEqual(response200Ok.body); + await expect(makeRequest()).resolves.toBe(200); - const statusCode = await page.evaluate(() => - fetch('/foo').then(res => res.status) - ); - await expect(statusCode).toBe(404); - expect(console.error).toHaveBeenCalled(); - }); + await expect(makeRequest()).resolves.toBe(404); + expect(console.error).toHaveBeenCalled(); + }); - it('matches only once every request in order with once set to true', async () => { - await mocketeer.mockREST( - requestGetFoo, - { status: 200, body: {} }, - { once: true } - ); - await mocketeer.mockREST( - requestGetFoo, - { status: 201, body: {} }, - { - once: true, - } - ); + it('matches only once request with once set to true', async () => { + await mocketeer.mockREST(requestGetFoo, { status: 200, body: {} }); - const firstResponseStatus = await page.evaluate(() => - fetch('/foo').then(res => res.status) - ); + await mocketeer.mockREST( + requestGetFoo, + { status: 201, body: {} }, + { + once: true, + } + ); - await expect(firstResponseStatus).toBe(200); + await expect(makeRequest()).resolves.toBe(201); + await expect(makeRequest()).resolves.toBe(200); + await expect(makeRequest()).resolves.toBe(200); + }); - const secondResponseStatus = await page.evaluate(() => - fetch('/foo').then(res => res.status) - ); + it('matches only once every request in order with once set to true', async () => { + await mocketeer.mockREST( + requestGetFoo, + { status: 200, body: {} }, + { once: true } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 201, body: {} }, + { + once: true, + } + ); + + await expect(makeRequest()).resolves.toBe(201); + await expect(makeRequest()).resolves.toBe(200); + }); + + it('matches newest request when added mock with same filter', async () => { + await mocketeer.mockREST(requestGetFoo, { status: 200, body: {} }); + await expect(makeRequest()).resolves.toBe(200); + + await mocketeer.mockREST(requestGetFoo, { status: 201, body: {} }); + await expect(makeRequest()).resolves.toBe(201); + }); + + it('matches newest request when multiple mocks have same filter', async () => { + await mocketeer.mockREST(requestGetFoo, { status: 200, body: {} }); + await mocketeer.mockREST(requestGetFoo, { status: 201, body: {} }); + + await expect(makeRequest()).resolves.toBe(201); + }); + + it('matches newest request when added mock with same filter and older mock has once set to true ', async () => { + await mocketeer.mockREST( + requestGetFoo, + { status: 200, body: {} }, + { once: true } + ); + await expect(makeRequest()).resolves.toBe(200); - await expect(secondResponseStatus).toBe(201); + await mocketeer.mockREST(requestGetFoo, { status: 201, body: {} }); + await expect(makeRequest()).resolves.toBe(201); + }); + + it('matches requests with once set to true in correct order when multiple mocks have same filter', async () => { + await mocketeer.mockREST( + requestGetFoo, + { status: 200, body: {} }, + { once: true } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 201, body: {} }, + { + once: true, + } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 202, body: {} }, + { + once: true, + } + ); + + await expect(makeRequest()).resolves.toBe(202); + await expect(makeRequest()).resolves.toBe(201); + await expect(makeRequest()).resolves.toBe(200); + }); + + it('matches request with highest priority when multiple mocks have same filter', async () => { + await mocketeer.mockREST( + requestGetFoo, + { status: 200, body: {} }, + { priority: 10 } + ); + + await mocketeer.mockREST(requestGetFoo, { status: 201, body: {} }); + + await mocketeer.mockREST( + requestGetFoo, + { status: 202, body: {} }, + { priority: 5 } + ); + + await expect(makeRequest()).resolves.toBe(200); + }); + + it('matches request in correct order with priority and once set to true when multiple mocks have same filter', async () => { + await mocketeer.mockREST( + requestGetFoo, + { status: 200, body: {} }, + { once: true } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 201, body: {} }, + { once: true, priority: 10 } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 202, body: {} }, + { once: true } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 203, body: {} }, + { once: true, priority: 10 } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 204, body: {} }, + { once: true, priority: 5 } + ); + + await mocketeer.mockREST( + requestGetFoo, + { status: 205, body: {} }, + { once: true } + ); + + await expect(makeRequest()).resolves.toBe(203); + await expect(makeRequest()).resolves.toBe(201); + await expect(makeRequest()).resolves.toBe(204); + await expect(makeRequest()).resolves.toBe(205); + await expect(makeRequest()).resolves.toBe(202); + await expect(makeRequest()).resolves.toBe(200); + }); }); }); diff --git a/test/unit/fixtures/request.ts b/test/unit/fixtures/request.ts index 84e7576..e60d3b3 100644 --- a/test/unit/fixtures/request.ts +++ b/test/unit/fixtures/request.ts @@ -1,4 +1,5 @@ import { Request } from 'puppeteer'; +import { MockOptions, RequestFilter, RestMock } from '../../../src'; export const createMockRequest = (): jest.Mocked => ({ postData: jest.fn().mockReturnValue(''), @@ -19,3 +20,23 @@ export const createMockRequest = (): jest.Mocked => ({ const a = createMockRequest(); a.headers.mockReturnValue({ a: '' }); + +const mockedResponse = { + status: 200, + body: {}, +}; + +export const createRestMock = ( + change: Partial = {}, + options?: Partial +) => { + return new RestMock( + { + url: '/foo', + method: 'GET', + ...change, + }, + mockedResponse, + options + ); +}; diff --git a/test/unit/http-mock.test.ts b/test/unit/http-mock.test.ts index 4ca1f41..a571542 100644 --- a/test/unit/http-mock.test.ts +++ b/test/unit/http-mock.test.ts @@ -1,29 +1,5 @@ -import { - MatchedRequest, - MockOptions, - RequestFilter, - RestMock, -} from '../../src'; - -const mockedResponse = { - status: 200, - body: {}, -}; - -const createRestMock = ( - change: Partial = {}, - options?: Partial -) => { - return new RestMock( - { - url: '/foo', - method: 'GET', - ...change, - }, - mockedResponse, - options - ); -}; +import { MatchedRequest } from '../../src'; +import { createRestMock } from './fixtures/request'; test('.getResponseForRequest matches GET request', () => { const mock = createRestMock(); @@ -293,41 +269,3 @@ test('.getResponseForRequest does not match second GET request when once option mock.getResponseForRequest(exampleRequest, 'http://example') ).toBeNull(); }); - -test('.sortByPriroty can be used to correctly sort mocks', () => { - const filter = { - url: '/foo', - method: 'GET', - }; - - let response = { - status: 200, - body: {}, - }; - - const mockDefault = new RestMock(filter, response); - const mock10 = new RestMock(filter, response, { - priority: 10, - }); - const mock5 = new RestMock(filter, response, { - priority: 5, - }); - - expect([mockDefault, mock10, mock5].sort(RestMock.sortByPriority)).toEqual([ - mock10, - mock5, - mockDefault, - ]); - - expect([mockDefault, mock5, mock10].sort(RestMock.sortByPriority)).toEqual([ - mock10, - mock5, - mockDefault, - ]); - - expect([mock10, mock5, mockDefault].sort(RestMock.sortByPriority)).toEqual([ - mock10, - mock5, - mockDefault, - ]); -}); diff --git a/test/unit/utils.test.ts b/test/unit/utils.test.ts index f6b4e0a..08db9ea 100644 --- a/test/unit/utils.test.ts +++ b/test/unit/utils.test.ts @@ -1,5 +1,9 @@ -import { requestToPlainObject, waitFor } from '../../src/utils'; -import { createMockRequest } from './fixtures/request'; +import { + requestToPlainObject, + waitFor, + addMockByPriority, +} from '../../src/utils'; +import { createMockRequest, createRestMock } from './fixtures/request'; describe('utils', () => { describe('requestToPlainObject', () => { @@ -79,4 +83,52 @@ describe('utils', () => { expect(e).not.toBeFalsy(); } }); + + describe('addMockByPriority', () => { + test('adds mocks with higher priority first', () => { + const mocks = [createRestMock()]; + const higherPriorityMock = createRestMock({}, { priority: 10 }); + + expect(addMockByPriority(mocks, higherPriorityMock)[0]).toBe( + higherPriorityMock + ); + }); + + test('adds mock in correct order basing on priority', () => { + const mocks = [createRestMock()]; + const higherPriorityMock = createRestMock({}, { priority: 10 }); + const middlePriorityMock = createRestMock({}, { priority: 5 }); + + expect(addMockByPriority(mocks, higherPriorityMock)[0]).toBe( + higherPriorityMock + ); + expect(addMockByPriority(mocks, middlePriorityMock)[1]).toBe( + middlePriorityMock + ); + }); + + test('adds mock to end when mock has lowest priority', () => { + const mocks = [ + createRestMock({}, { priority: 10 }), + createRestMock({}, { priority: 5 }), + ]; + const lowestPriorityMock = createRestMock({}, { priority: 3 }); + + expect(addMockByPriority(mocks, lowestPriorityMock)[2]).toBe( + lowestPriorityMock + ); + }); + + test('adds mock before mock with same priority', () => { + const mocks = [ + createRestMock({}, { priority: 10 }), + createRestMock({}, { priority: 5 }), + ]; + const samePriorityMock = createRestMock({}, { priority: 5 }); + + expect(addMockByPriority(mocks, samePriorityMock)[1]).toBe( + samePriorityMock + ); + }); + }); });