From 74901d36804964752ddbd0c827e804c5a841c2e9 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 10 Sep 2021 13:46:30 +0100 Subject: [PATCH 01/15] add datamocks --- package.json | 1 + yarn.lock | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/package.json b/package.json index cbf0f49..beda6b3 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "fetch-mock": "^9.4.0", + "mock-socket": "^9.0.3", "query-string": "^5.1.1", "xhr-mock": "^2.5.1" }, diff --git a/yarn.lock b/yarn.lock index 8c12ec6..3251c1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,6 +3431,13 @@ mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mock-socket@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.0.3.tgz#4bc6d2aea33191e4fed5ec71f039e2bbeb95e414" + integrity sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw== + dependencies: + url-parse "^1.4.4" + mri@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.5.tgz#ce21dba2c69f74a9b7cf8a1ec62307e089e223e0" @@ -3918,6 +3925,11 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + react-is@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -4101,6 +4113,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4739,6 +4756,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse@^1.4.4: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" From 0d3754793f44019cc81ddea6cb5e60b6c77cc24e Mon Sep 17 00:00:00 2001 From: Aiden Date: Mon, 13 Sep 2021 14:07:48 +0100 Subject: [PATCH 02/15] a test --- jest.config.js | 3 +- src/mocks.test.ts | 558 ++++------------------------------------------ src/mocks.ts | 3 +- src/types.ts | 10 +- tsconfig.json | 4 +- 5 files changed, 60 insertions(+), 518 deletions(-) diff --git a/jest.config.js b/jest.config.js index ed0f2e1..96738a6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ module.exports = { preset: 'ts-jest', - testEnvironment: 'jsdom' + testEnvironment: 'jsdom', + verbose: true, }; diff --git a/src/mocks.test.ts b/src/mocks.test.ts index 9670564..dc684ac 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -5,534 +5,68 @@ import { extractScenarioFromLocation, reduceAllMocksForScenario, } from './mocks'; -import { HttpMethod, Scenarios, MockConfig } from './types'; +import { + HttpMethod, + Scenarios, + MockConfig, + WebSocketMock, + WebSocketServerMock, +} from './types'; import XHRMock, { proxy } from 'xhr-mock'; -import fetchMock from 'fetch-mock'; +import fetchMock, { done as done2 } from 'fetch-mock'; +import { Server as MockSever } from 'mock-socket'; describe('data-mocks', () => { beforeEach(() => { fetchMock.resetHistory(); }); - describe('REST', () => { - describe('HTTP methods', () => { - const httpMethods: HttpMethod[] = [ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE', - ]; - - httpMethods.forEach((httpMethod) => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: httpMethod, - response: {}, - responseCode: 200, - }, - { - url: /bar/, - method: httpMethod, - response: {}, - responseCode: 200, - }, - ], - }; - - test(`Mocks calls for ${httpMethod}`, () => { - const fetchSpy = jest.spyOn( - fetchMock, - httpMethod.toLowerCase() as any - ); - const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); - - injectMocks(scenarios, 'default'); - - expect(fetchSpy).toHaveBeenCalledTimes(2); - expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); - expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); - - expect(xhrSpy).toHaveBeenCalledTimes(2); - expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); - expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); - }); - }); - }); - - describe('Fetch mocks', () => { - const getMatcher = /get-endpoint/; - const postMatcher = /post-endpoint/; - const putMatcher = /put-endpoint/; - const patchMatcher = /patch-endpoint/; - const deleteMatcher = /delete-endpoint/; - - const scenarios: Scenarios = { - default: [ - { - url: getMatcher, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: postMatcher, - method: 'POST', - response: {}, - responseCode: 200, - responseHeaders: {}, - }, - { - url: putMatcher, - method: 'PUT', - response: {}, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: patchMatcher, - method: 'PATCH', - response: {}, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: deleteMatcher, - method: 'DELETE', - response: {}, - responseCode: 200, - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); - }); - - test(`Correct endpoints being called for mocked fetch endpoints`, async () => { - expect(fetchMock.calls().length).toEqual(0); - - await fetch('/get-endpoint', { method: 'GET' }); - expect(fetchMock.called(getMatcher)).toBeTruthy(); - - await fetch('/post-endpoint', { method: 'POST' }); - expect(fetchMock.called(postMatcher)).toBeTruthy(); - - await fetch('/put-endpoint', { method: 'PUT' }); - expect(fetchMock.called(putMatcher)).toBeTruthy(); - - await fetch('/patch-endpoint', { method: 'PATCH' }); - expect(fetchMock.called(patchMatcher)).toBeTruthy(); - - await fetch('/delete-endpoint', { method: 'DELETE' }); - expect(fetchMock.called(deleteMatcher)).toBeTruthy(); - }); + describe.only('Websocket Server', () => { + let server: MockSever; + beforeAll(async () => { + server = await createServer(); }); - describe('XHR mocks', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: { foo: 'GET' }, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /foo/, - method: 'POST', - response: { foo: 'POST' }, - responseCode: 200, - responseHeaders: {}, - }, - { - url: /foo/, - method: 'PUT', - response: { foo: 'PUT' }, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: /foo/, - method: 'PATCH', - response: { foo: 'PATCH' }, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: /foo/, - method: 'DELETE', - response: { foo: 'DELETE' }, - responseCode: 200, - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); + test('WS echo server can be mocked and is responsive', async () => { + const testURI = 'ws://localhost/foo'; + const socket = new WebSocket(testURI); + await awaitSocket(socket, socket.OPEN); + let res; + socket.addEventListener('message', (data) => { + res = data.data; + socket.close(); }); - test(`Correct response for mocked XHR endpoints`, async () => { - const resGET = await axios.get('/foo'); - expect(resGET.data).toEqual({ foo: 'GET' }); - expect(resGET.headers).toEqual({ token: 'foo' }); - - const resPOST = await axios.post('/foo'); - expect(resPOST.data).toEqual({ foo: 'POST' }); - expect(resPOST.headers).toEqual({}); - - const resPUT = await axios.put('/foo'); - expect(resPUT.data).toEqual({ foo: 'PUT' }); - expect(resPUT.headers).toEqual({}); + const message = 'hello'; + socket.send(message); - const resPATCH = await axios.patch('/foo'); - expect(resPATCH.data).toEqual({ foo: 'PATCH' }); - expect(resPATCH.headers).toEqual({}); - - const resDELETE = await axios.delete('/foo'); - expect(resDELETE.data).toEqual({ foo: 'DELETE' }); - expect(resDELETE.headers).toEqual({}); - }); + await awaitSocket(socket, socket.CLOSED); + return expect(res).toBe(message); }); }); +}); - describe('GraphQL mocks', () => { - const graphQLMatcher = /graphql/; - const scenarios: Scenarios = { - default: [ - { - url: /graphql/, - method: 'GRAPHQL', - operations: [ - { - operationName: 'QueryTest', - type: 'query', - response: { data: { test: 'query test' } }, - responseHeaders: { token: 'foo' }, - }, - { - operationName: 'MutationTest', - type: 'mutation', - response: { data: { test: 'mutation test' } }, - responseHeaders: { token: 'bar' }, - }, - ], - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); - }); - - describe('Fetch', () => { - test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { - expect(fetchMock.calls().length).toEqual(0); - - await fetch('https://www.test.com/graphql', { method: 'GET' }); - expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); - }); - - test('Correct response when using GET request', async () => { - const response = await fetch( - 'https://www.test.com/graphql?query={}&operationName=QueryTest', - { method: 'GET' } - ); - const responseBody = await response.json(); - expect(responseBody.data).toEqual({ test: 'query test' }); - expect(response.headers.get('token')).toEqual('foo'); - }); - - test('Correct response when using POST request', async () => { - const response = await fetch('https://www.test.com/graphql', { - method: 'POST', - body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), - }); - const responseBody = await response.json(); - expect(responseBody.data).toEqual({ test: 'mutation test' }); - expect(response.headers.get('token')).toEqual('bar'); - }); - }); - - describe('XHR', () => { - test('Correct response when using GET request', async () => { - const { data, headers } = await axios.get( - 'https://www.test.com/graphql?query={}&operationName=QueryTest' - ); - expect(data.data).toEqual({ test: 'query test' }); - expect(headers).toEqual({ token: 'foo' }); - }); - - test('Correct response when using POST request', async () => { - const { data, headers } = await axios.post( - 'https://www.test.com/graphql', - { - query: '{}', - operationName: 'MutationTest', - } - ); - expect(data.data).toEqual({ test: 'mutation test' }); - expect(headers).toEqual({ token: 'bar' }); +const createServer = () => { + const testURI = 'ws://localhost/foo'; + const testServerMock: WebSocketServerMock = (server) => () => { + server.on('connection', (socket) => { + socket.on('message', (data) => { + socket.send(data.toString()); }); }); - }); - - describe('Scenarios', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'bar' }, - }, - { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - ], - someScenario: [ - { - url: /bar/, - method: 'GET', - response: { some: 'otherResponse' }, - responseCode: 401, - }, - { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - ], - }; - - test(`Can extract the scenario from anywhere in the URL`, () => { - window.history.pushState( - {}, - 'Test', - '/?foo=bar&baz=bar&scenario=someScenario' - ); - const scenario = extractScenarioFromLocation(window.location); - expect(scenario).toEqual('someScenario'); - }); - - test(`Can create a list for the default scenario`, () => { - const result = reduceAllMocksForScenario(scenarios, 'default'); - - expect(result).toEqual([ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'bar' }, - }, - { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - ]); - }); - - test(`Can create a list of mocks for a specific scenario`, () => { - const result = reduceAllMocksForScenario(scenarios, 'someScenario'); - - expect(result).toEqual([ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: { some: 'otherResponse' }, - responseCode: 401, - }, - { - url: /bar/, - method: 'POST', - response: {}, - responseCode: 200, - }, - { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - ]); - }); - - test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { - const scenarios: Scenarios = { - default: [ - { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, - ], - scenario: [], - }; - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toEqual(scenarios.default); - }); - - test(`Returns empty array if default and scenario mocks are not defined`, () => { - const scenarios: Scenarios = { - default: [], - scenario: [], - }; - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toEqual([]); - }); - - test(`Preserves any flags defined in the url regex`, () => { - const scenarios: Scenarios = { - default: [], - scenario: [ - { - url: /^\/foo/i, - method: 'GET', - response: {}, - responseCode: 200, - }, - { - url: /^\/graphql/i, - method: 'GRAPHQL', - operations: [ - { - operationName: 'Query', - type: 'query', - response: { data: { test: 'data' } }, - }, - ], - }, - ], - }; - expect('/Foo').toMatch(scenarios.scenario[0].url); - expect('/GraphQL').toMatch(scenarios.scenario[1].url); - - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toHaveLength(2); - expect('/Foo').toMatch(result[0].url); - expect('/GraphQL').toMatch(result[1].url); - }); - }); - - describe('Utility: extractScenarioFromLocation', () => { - test(`Correct scenario name is returned`, () => { - window.history.pushState({}, 'Test', '/?scenario=test'); - expect(extractScenarioFromLocation(window.location)).toBe('test'); - }); - - test(`Default scenario name is returned`, () => { - window.history.pushState({}, 'Test', '/'); - expect(extractScenarioFromLocation(window.location)).toBe('default'); - }); - - test(`Throws error if user uses more than one scenario at a time`, () => { - window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); - expect(() => extractScenarioFromLocation(window.location)).toThrowError( - 'Only one scenario may be used at a time' - ); - }); - }); - - describe('Config', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: { foo: 'GET' }, - responseCode: 200, - }, - ], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - - injectMocks(scenarios, 'default', mockConfig); - expect(xhrSpy).toHaveBeenCalledTimes(2); - expect(xhrSpy).toHaveBeenCalledWith(proxy); - }); - - test('Does not set XHR proxy if allowXHRPassthrough is false', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - const mockConfig: MockConfig = { - allowXHRPassthrough: false, - }; - - injectMocks(scenarios, 'default', mockConfig); - expect(xhrSpy).toHaveBeenCalledTimes(1); - expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - }); - - test('Does not set XHR proxy if config is not passed', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - injectMocks(scenarios, 'default'); - expect(xhrSpy).toHaveBeenCalledTimes(1); - expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - }); - - test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { - const mockConfig: MockConfig = { - allowFetchPassthrough: false, - }; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(false); - }); - - test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { - const mockConfig: MockConfig = {}; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(false); - }); - - test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { - const mockConfig: MockConfig = { - allowFetchPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(true); - }); - - test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - const resGET = await axios.get('/foo'); - expect(resGET.data).toEqual({ foo: 'GET' }); - }); - - test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { - // We only expect an error in this case becaue the route does not exist. - // We just want to see if we attempted a real network request here. - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - try { - await axios.get('/bar'); - } catch (e) { - expect(e.message).toContain('Network Error'); + return server; + }; + return testServerMock(new MockSever(testURI))(); +}; + +const awaitSocket = (socket, state) => { + return new Promise(function (resolve) { + setTimeout(function () { + if (socket.readyState === state) { + resolve(true); + } else { + awaitSocket(socket, state).then(resolve); } - }); + }, 1000); }); -}); +}; diff --git a/src/mocks.ts b/src/mocks.ts index c954f35..3c47a91 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -188,8 +188,7 @@ function handleRestMock({ } } -/** - * Mocks the right HTTP method for a GraphQL mock. +/** const graphQLMocks = mocks.filter(ethod for a GraphQL mock. */ function handleGraphQLMock({ url, operations }: GraphQLMock) { const graphQLErrorResponse = { errors: [] }; diff --git a/src/types.ts b/src/types.ts index 06271e1..5b25dc0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ export { injectMocks, reduceAllMocksForScenario, } from './mocks'; +import { Server as MockServer } from 'mock-socket'; export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; @@ -27,6 +28,13 @@ export type GraphQLMock = { operations: Array; }; +export type WebSocketServerMock = (mockServer: MockServer) => () => MockServer; +export type WebSocketMock = { + url: RegExp; + method: 'WEBSOCKET'; + server: WebSocketServerMock; +}; + export type Operation = { type: 'query' | 'mutation'; operationName: string; @@ -36,7 +44,7 @@ export type Operation = { delay?: number; }; -export type Mock = HttpMock | GraphQLMock; +export type Mock = HttpMock | GraphQLMock | WebSocketMock; export type Scenarios = { default: Mock[]; diff --git a/tsconfig.json b/tsconfig.json index 57fe7d8..226165b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ "removeComments": true /* Do not emit comments to output. */, "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, - "noUnusedLocals": true /* Report errors on unused locals. */, - "noUnusedParameters": true /* Report errors on unused parameters. */, + "noUnusedLocals": false /* Report errors on unused locals. */, + "noUnusedParameters": false /* Report errors on unused parameters. */, "lib": ["es2017", "dom"], "esModuleInterop": true }, From 2d4ef76d536bb6c3651f64e1fcf100427668f8bb Mon Sep 17 00:00:00 2001 From: Aiden Date: Mon, 13 Sep 2021 15:09:14 +0100 Subject: [PATCH 03/15] wip --- src/mocks.test.ts | 563 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 543 insertions(+), 20 deletions(-) diff --git a/src/mocks.test.ts b/src/mocks.test.ts index dc684ac..b9f23bc 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -1,47 +1,570 @@ -import 'isomorphic-fetch'; import axios from 'axios'; +import fetchMock from 'fetch-mock'; +import 'isomorphic-fetch'; +import { Server as MockSever } from 'mock-socket'; +import XHRMock, { proxy } from 'xhr-mock'; import { - injectMocks, extractScenarioFromLocation, + injectMocks, reduceAllMocksForScenario, } from './mocks'; import { HttpMethod, - Scenarios, MockConfig, - WebSocketMock, + Scenarios, WebSocketServerMock, } from './types'; -import XHRMock, { proxy } from 'xhr-mock'; -import fetchMock, { done as done2 } from 'fetch-mock'; -import { Server as MockSever } from 'mock-socket'; describe('data-mocks', () => { beforeEach(() => { fetchMock.resetHistory(); }); - describe.only('Websocket Server', () => { + describe.only('Websockets', () => { let server: MockSever; beforeAll(async () => { server = await createServer(); }); - test('WS echo server can be mocked and is responsive', async () => { - const testURI = 'ws://localhost/foo'; - const socket = new WebSocket(testURI); - await awaitSocket(socket, socket.OPEN); - let res; - socket.addEventListener('message', (data) => { - res = data.data; - socket.close(); + describe.only('Websocket server', () => { + test('WS echo server can be mocked and is responsive', async () => { + const testURI = 'ws://localhost/foo'; + const socket = new WebSocket(testURI); + await awaitSocket(socket, socket.OPEN); + let res; + socket.addEventListener('message', (data) => { + res = data.data; + socket.close(); + }); + + const message = 'hello'; + socket.send(message); + + await awaitSocket(socket, socket.CLOSED); + return expect(res).toBe(message); + }); + }); + }); + + describe('REST', () => { + describe('HTTP methods', () => { + const httpMethods: HttpMethod[] = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + ]; + + httpMethods.forEach((httpMethod) => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: httpMethod, + response: {}, + responseCode: 200, + }, + { + url: /bar/, + method: httpMethod, + response: {}, + responseCode: 200, + }, + ], + }; + + test(`Mocks calls for ${httpMethod}`, () => { + const fetchSpy = jest.spyOn( + fetchMock, + httpMethod.toLowerCase() as any + ); + const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); + + injectMocks(scenarios, 'default'); + + expect(fetchSpy).toHaveBeenCalledTimes(2); + expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); + expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); + + expect(xhrSpy).toHaveBeenCalledTimes(2); + expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); + expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); + }); + }); + }); + + describe('Fetch mocks', () => { + const getMatcher = /get-endpoint/; + const postMatcher = /post-endpoint/; + const putMatcher = /put-endpoint/; + const patchMatcher = /patch-endpoint/; + const deleteMatcher = /delete-endpoint/; + + const scenarios: Scenarios = { + default: [ + { + url: getMatcher, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: postMatcher, + method: 'POST', + response: {}, + responseCode: 200, + responseHeaders: {}, + }, + { + url: putMatcher, + method: 'PUT', + response: {}, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: patchMatcher, + method: 'PATCH', + response: {}, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: deleteMatcher, + method: 'DELETE', + response: {}, + responseCode: 200, + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); + }); + + test(`Correct endpoints being called for mocked fetch endpoints`, async () => { + expect(fetchMock.calls().length).toEqual(0); + + await fetch('/get-endpoint', { method: 'GET' }); + expect(fetchMock.called(getMatcher)).toBeTruthy(); + + await fetch('/post-endpoint', { method: 'POST' }); + expect(fetchMock.called(postMatcher)).toBeTruthy(); + + await fetch('/put-endpoint', { method: 'PUT' }); + expect(fetchMock.called(putMatcher)).toBeTruthy(); + + await fetch('/patch-endpoint', { method: 'PATCH' }); + expect(fetchMock.called(patchMatcher)).toBeTruthy(); + + await fetch('/delete-endpoint', { method: 'DELETE' }); + expect(fetchMock.called(deleteMatcher)).toBeTruthy(); + }); + }); + + describe('XHR mocks', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: { foo: 'GET' }, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /foo/, + method: 'POST', + response: { foo: 'POST' }, + responseCode: 200, + responseHeaders: {}, + }, + { + url: /foo/, + method: 'PUT', + response: { foo: 'PUT' }, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: /foo/, + method: 'PATCH', + response: { foo: 'PATCH' }, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: /foo/, + method: 'DELETE', + response: { foo: 'DELETE' }, + responseCode: 200, + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); + }); + + test(`Correct response for mocked XHR endpoints`, async () => { + const resGET = await axios.get('/foo'); + expect(resGET.data).toEqual({ foo: 'GET' }); + expect(resGET.headers).toEqual({ token: 'foo' }); + + const resPOST = await axios.post('/foo'); + expect(resPOST.data).toEqual({ foo: 'POST' }); + expect(resPOST.headers).toEqual({}); + + const resPUT = await axios.put('/foo'); + expect(resPUT.data).toEqual({ foo: 'PUT' }); + expect(resPUT.headers).toEqual({}); + + const resPATCH = await axios.patch('/foo'); + expect(resPATCH.data).toEqual({ foo: 'PATCH' }); + expect(resPATCH.headers).toEqual({}); + + const resDELETE = await axios.delete('/foo'); + expect(resDELETE.data).toEqual({ foo: 'DELETE' }); + expect(resDELETE.headers).toEqual({}); + }); + }); + }); + + describe('GraphQL mocks', () => { + const graphQLMatcher = /graphql/; + const scenarios: Scenarios = { + default: [ + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'QueryTest', + type: 'query', + response: { data: { test: 'query test' } }, + responseHeaders: { token: 'foo' }, + }, + { + operationName: 'MutationTest', + type: 'mutation', + response: { data: { test: 'mutation test' } }, + responseHeaders: { token: 'bar' }, + }, + ], + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); + }); + + describe('Fetch', () => { + test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { + expect(fetchMock.calls().length).toEqual(0); + + await fetch('https://www.test.com/graphql', { method: 'GET' }); + expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); + }); + + test('Correct response when using GET request', async () => { + const response = await fetch( + 'https://www.test.com/graphql?query={}&operationName=QueryTest', + { method: 'GET' } + ); + const responseBody = await response.json(); + expect(responseBody.data).toEqual({ test: 'query test' }); + expect(response.headers.get('token')).toEqual('foo'); + }); + + test('Correct response when using POST request', async () => { + const response = await fetch('https://www.test.com/graphql', { + method: 'POST', + body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), + }); + const responseBody = await response.json(); + expect(responseBody.data).toEqual({ test: 'mutation test' }); + expect(response.headers.get('token')).toEqual('bar'); + }); + }); + + describe('XHR', () => { + test('Correct response when using GET request', async () => { + const { data, headers } = await axios.get( + 'https://www.test.com/graphql?query={}&operationName=QueryTest' + ); + expect(data.data).toEqual({ test: 'query test' }); + expect(headers).toEqual({ token: 'foo' }); }); - const message = 'hello'; - socket.send(message); + test('Correct response when using POST request', async () => { + const { data, headers } = await axios.post( + 'https://www.test.com/graphql', + { + query: '{}', + operationName: 'MutationTest', + } + ); + expect(data.data).toEqual({ test: 'mutation test' }); + expect(headers).toEqual({ token: 'bar' }); + }); + }); + }); + + describe('Scenarios', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'bar' }, + }, + { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + ], + someScenario: [ + { + url: /bar/, + method: 'GET', + response: { some: 'otherResponse' }, + responseCode: 401, + }, + { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + ], + }; + + test(`Can extract the scenario from anywhere in the URL`, () => { + window.history.pushState( + {}, + 'Test', + '/?foo=bar&baz=bar&scenario=someScenario' + ); + const scenario = extractScenarioFromLocation(window.location); + expect(scenario).toEqual('someScenario'); + }); + + test(`Can create a list for the default scenario`, () => { + const result = reduceAllMocksForScenario(scenarios, 'default'); + + expect(result).toEqual([ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'bar' }, + }, + { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + ]); + }); + + test(`Can create a list of mocks for a specific scenario`, () => { + const result = reduceAllMocksForScenario(scenarios, 'someScenario'); + + expect(result).toEqual([ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: { some: 'otherResponse' }, + responseCode: 401, + }, + { + url: /bar/, + method: 'POST', + response: {}, + responseCode: 200, + }, + { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + ]); + }); + + test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { + const scenarios: Scenarios = { + default: [ + { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, + ], + scenario: [], + }; + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toEqual(scenarios.default); + }); + + test(`Returns empty array if default and scenario mocks are not defined`, () => { + const scenarios: Scenarios = { + default: [], + scenario: [], + }; + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toEqual([]); + }); + + test(`Preserves any flags defined in the url regex`, () => { + const scenarios: Scenarios = { + default: [], + scenario: [ + { + url: /^\/foo/i, + method: 'GET', + response: {}, + responseCode: 200, + }, + { + url: /^\/graphql/i, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'data' } }, + }, + ], + }, + ], + }; + expect('/Foo').toMatch(scenarios.scenario[0].url); + expect('/GraphQL').toMatch(scenarios.scenario[1].url); + + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toHaveLength(2); + expect('/Foo').toMatch(result[0].url); + expect('/GraphQL').toMatch(result[1].url); + }); + }); + + describe('Utility: extractScenarioFromLocation', () => { + test(`Correct scenario name is returned`, () => { + window.history.pushState({}, 'Test', '/?scenario=test'); + expect(extractScenarioFromLocation(window.location)).toBe('test'); + }); + + test(`Default scenario name is returned`, () => { + window.history.pushState({}, 'Test', '/'); + expect(extractScenarioFromLocation(window.location)).toBe('default'); + }); + + test(`Throws error if user uses more than one scenario at a time`, () => { + window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); + expect(() => extractScenarioFromLocation(window.location)).toThrowError( + 'Only one scenario may be used at a time' + ); + }); + }); + + describe('Config', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: { foo: 'GET' }, + responseCode: 200, + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); - await awaitSocket(socket, socket.CLOSED); - return expect(res).toBe(message); + test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + + injectMocks(scenarios, 'default', mockConfig); + expect(xhrSpy).toHaveBeenCalledTimes(2); + expect(xhrSpy).toHaveBeenCalledWith(proxy); + }); + + test('Does not set XHR proxy if allowXHRPassthrough is false', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + const mockConfig: MockConfig = { + allowXHRPassthrough: false, + }; + + injectMocks(scenarios, 'default', mockConfig); + expect(xhrSpy).toHaveBeenCalledTimes(1); + expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + }); + + test('Does not set XHR proxy if config is not passed', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + injectMocks(scenarios, 'default'); + expect(xhrSpy).toHaveBeenCalledTimes(1); + expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + }); + + test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { + const mockConfig: MockConfig = { + allowFetchPassthrough: false, + }; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(false); + }); + + test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { + const mockConfig: MockConfig = {}; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(false); + }); + + test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { + const mockConfig: MockConfig = { + allowFetchPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(true); + }); + + test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + const resGET = await axios.get('/foo'); + expect(resGET.data).toEqual({ foo: 'GET' }); + }); + + test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { + // We only expect an error in this case becaue the route does not exist. + // We just want to see if we attempted a real network request here. + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + try { + await axios.get('/bar'); + } catch (e) { + expect(e.message).toContain('Network Error'); + } }); }); }); From b71c0191f33b3994b9a3485c25c67e5780625a2d Mon Sep 17 00:00:00 2001 From: Aiden Date: Mon, 13 Sep 2021 17:50:10 +0100 Subject: [PATCH 04/15] wft --- src/mocks.test.ts | 1083 +++++++++++++++++++++++---------------------- src/mocks.ts | 9 +- src/types.ts | 2 +- 3 files changed, 563 insertions(+), 531 deletions(-) diff --git a/src/mocks.test.ts b/src/mocks.test.ts index b9f23bc..15d149e 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -1,8 +1,8 @@ import axios from 'axios'; import fetchMock from 'fetch-mock'; -import 'isomorphic-fetch'; -import { Server as MockSever } from 'mock-socket'; -import XHRMock, { proxy } from 'xhr-mock'; +// import 'isomorphic-fetch'; +import { Server as MockSever, WebSocket as ServerWebsocket } from 'mock-socket'; +// import XHRMock, { proxy } from 'xhr-mock'; import { extractScenarioFromLocation, injectMocks, @@ -20,13 +20,13 @@ describe('data-mocks', () => { fetchMock.resetHistory(); }); - describe.only('Websockets', () => { - let server: MockSever; + describe('Websockets', () => { + let server: WebSocketServerMock; beforeAll(async () => { server = await createServer(); }); - describe.only('Websocket server', () => { + describe('Websocket server', () => { test('WS echo server can be mocked and is responsive', async () => { const testURI = 'ws://localhost/foo'; const socket = new WebSocket(testURI); @@ -44,534 +44,559 @@ describe('data-mocks', () => { return expect(res).toBe(message); }); }); + // describe('Mock from scenario', () => { + // it('works', async () => { + // const onMessage = jest.fn() + // const onConnect = (socket: ServerWebsocket) => socket.on('message', onMessage) + // const onConnectSpy = jest.spyOn({ onConnect }, 'onConnect') + // const onMessageSpy = () => jest.spyOn({ onMessage }, 'onMessage') + + // const scenarios: Scenarios = { + // default: [ + // { + // url: /foo/, + // method: 'WEBSOCKET', + // server: (s) => { + // s.on('connection', onConnect); + // } + // } + // ], + // }; + // injectMocks(scenarios, 'default'); + // const socket = new WebSocket('/foo') + // await awaitSocket(socket, WebSocket.OPEN) + // expect(onConnectSpy).toBeCalled() + + // }) + + // }) }); - describe('REST', () => { - describe('HTTP methods', () => { - const httpMethods: HttpMethod[] = [ - 'GET', - 'POST', - 'PUT', - 'PATCH', - 'DELETE', - ]; - - httpMethods.forEach((httpMethod) => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: httpMethod, - response: {}, - responseCode: 200, - }, - { - url: /bar/, - method: httpMethod, - response: {}, - responseCode: 200, - }, - ], - }; - - test(`Mocks calls for ${httpMethod}`, () => { - const fetchSpy = jest.spyOn( - fetchMock, - httpMethod.toLowerCase() as any - ); - const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); - - injectMocks(scenarios, 'default'); - - expect(fetchSpy).toHaveBeenCalledTimes(2); - expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); - expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); - - expect(xhrSpy).toHaveBeenCalledTimes(2); - expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); - expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); - }); - }); - }); - - describe('Fetch mocks', () => { - const getMatcher = /get-endpoint/; - const postMatcher = /post-endpoint/; - const putMatcher = /put-endpoint/; - const patchMatcher = /patch-endpoint/; - const deleteMatcher = /delete-endpoint/; - - const scenarios: Scenarios = { - default: [ - { - url: getMatcher, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: postMatcher, - method: 'POST', - response: {}, - responseCode: 200, - responseHeaders: {}, - }, - { - url: putMatcher, - method: 'PUT', - response: {}, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: patchMatcher, - method: 'PATCH', - response: {}, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: deleteMatcher, - method: 'DELETE', - response: {}, - responseCode: 200, - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); - }); - - test(`Correct endpoints being called for mocked fetch endpoints`, async () => { - expect(fetchMock.calls().length).toEqual(0); - - await fetch('/get-endpoint', { method: 'GET' }); - expect(fetchMock.called(getMatcher)).toBeTruthy(); - - await fetch('/post-endpoint', { method: 'POST' }); - expect(fetchMock.called(postMatcher)).toBeTruthy(); - - await fetch('/put-endpoint', { method: 'PUT' }); - expect(fetchMock.called(putMatcher)).toBeTruthy(); - - await fetch('/patch-endpoint', { method: 'PATCH' }); - expect(fetchMock.called(patchMatcher)).toBeTruthy(); - - await fetch('/delete-endpoint', { method: 'DELETE' }); - expect(fetchMock.called(deleteMatcher)).toBeTruthy(); - }); - }); - - describe('XHR mocks', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: { foo: 'GET' }, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /foo/, - method: 'POST', - response: { foo: 'POST' }, - responseCode: 200, - responseHeaders: {}, - }, - { - url: /foo/, - method: 'PUT', - response: { foo: 'PUT' }, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: /foo/, - method: 'PATCH', - response: { foo: 'PATCH' }, - responseCode: 200, - responseHeaders: undefined, - }, - { - url: /foo/, - method: 'DELETE', - response: { foo: 'DELETE' }, - responseCode: 200, - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); - }); - - test(`Correct response for mocked XHR endpoints`, async () => { - const resGET = await axios.get('/foo'); - expect(resGET.data).toEqual({ foo: 'GET' }); - expect(resGET.headers).toEqual({ token: 'foo' }); - - const resPOST = await axios.post('/foo'); - expect(resPOST.data).toEqual({ foo: 'POST' }); - expect(resPOST.headers).toEqual({}); - - const resPUT = await axios.put('/foo'); - expect(resPUT.data).toEqual({ foo: 'PUT' }); - expect(resPUT.headers).toEqual({}); - - const resPATCH = await axios.patch('/foo'); - expect(resPATCH.data).toEqual({ foo: 'PATCH' }); - expect(resPATCH.headers).toEqual({}); - - const resDELETE = await axios.delete('/foo'); - expect(resDELETE.data).toEqual({ foo: 'DELETE' }); - expect(resDELETE.headers).toEqual({}); - }); - }); - }); - - describe('GraphQL mocks', () => { - const graphQLMatcher = /graphql/; - const scenarios: Scenarios = { - default: [ - { - url: /graphql/, - method: 'GRAPHQL', - operations: [ - { - operationName: 'QueryTest', - type: 'query', - response: { data: { test: 'query test' } }, - responseHeaders: { token: 'foo' }, - }, - { - operationName: 'MutationTest', - type: 'mutation', - response: { data: { test: 'mutation test' } }, - responseHeaders: { token: 'bar' }, - }, - ], - }, - ], - }; - - beforeAll(() => { - injectMocks(scenarios, 'default'); - }); - - describe('Fetch', () => { - test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { - expect(fetchMock.calls().length).toEqual(0); - - await fetch('https://www.test.com/graphql', { method: 'GET' }); - expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); - }); - - test('Correct response when using GET request', async () => { - const response = await fetch( - 'https://www.test.com/graphql?query={}&operationName=QueryTest', - { method: 'GET' } - ); - const responseBody = await response.json(); - expect(responseBody.data).toEqual({ test: 'query test' }); - expect(response.headers.get('token')).toEqual('foo'); - }); - - test('Correct response when using POST request', async () => { - const response = await fetch('https://www.test.com/graphql', { - method: 'POST', - body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), - }); - const responseBody = await response.json(); - expect(responseBody.data).toEqual({ test: 'mutation test' }); - expect(response.headers.get('token')).toEqual('bar'); - }); - }); - - describe('XHR', () => { - test('Correct response when using GET request', async () => { - const { data, headers } = await axios.get( - 'https://www.test.com/graphql?query={}&operationName=QueryTest' - ); - expect(data.data).toEqual({ test: 'query test' }); - expect(headers).toEqual({ token: 'foo' }); - }); - - test('Correct response when using POST request', async () => { - const { data, headers } = await axios.post( - 'https://www.test.com/graphql', - { - query: '{}', - operationName: 'MutationTest', - } - ); - expect(data.data).toEqual({ test: 'mutation test' }); - expect(headers).toEqual({ token: 'bar' }); - }); - }); - }); - - describe('Scenarios', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'bar' }, - }, - { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - ], - someScenario: [ - { - url: /bar/, - method: 'GET', - response: { some: 'otherResponse' }, - responseCode: 401, - }, - { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - ], - }; - - test(`Can extract the scenario from anywhere in the URL`, () => { - window.history.pushState( - {}, - 'Test', - '/?foo=bar&baz=bar&scenario=someScenario' - ); - const scenario = extractScenarioFromLocation(window.location); - expect(scenario).toEqual('someScenario'); - }); - - test(`Can create a list for the default scenario`, () => { - const result = reduceAllMocksForScenario(scenarios, 'default'); - - expect(result).toEqual([ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'bar' }, - }, - { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - ]); - }); - - test(`Can create a list of mocks for a specific scenario`, () => { - const result = reduceAllMocksForScenario(scenarios, 'someScenario'); - - expect(result).toEqual([ - { - url: /foo/, - method: 'GET', - response: {}, - responseCode: 200, - responseHeaders: { token: 'foo' }, - }, - { - url: /bar/, - method: 'GET', - response: { some: 'otherResponse' }, - responseCode: 401, - }, - { - url: /bar/, - method: 'POST', - response: {}, - responseCode: 200, - }, - { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - ]); - }); - - test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { - const scenarios: Scenarios = { - default: [ - { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, - ], - scenario: [], - }; - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toEqual(scenarios.default); - }); - - test(`Returns empty array if default and scenario mocks are not defined`, () => { - const scenarios: Scenarios = { - default: [], - scenario: [], - }; - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toEqual([]); - }); - - test(`Preserves any flags defined in the url regex`, () => { - const scenarios: Scenarios = { - default: [], - scenario: [ - { - url: /^\/foo/i, - method: 'GET', - response: {}, - responseCode: 200, - }, - { - url: /^\/graphql/i, - method: 'GRAPHQL', - operations: [ - { - operationName: 'Query', - type: 'query', - response: { data: { test: 'data' } }, - }, - ], - }, - ], - }; - expect('/Foo').toMatch(scenarios.scenario[0].url); - expect('/GraphQL').toMatch(scenarios.scenario[1].url); - - const result = reduceAllMocksForScenario(scenarios, 'scenario'); - expect(result).toHaveLength(2); - expect('/Foo').toMatch(result[0].url); - expect('/GraphQL').toMatch(result[1].url); - }); - }); - - describe('Utility: extractScenarioFromLocation', () => { - test(`Correct scenario name is returned`, () => { - window.history.pushState({}, 'Test', '/?scenario=test'); - expect(extractScenarioFromLocation(window.location)).toBe('test'); - }); - - test(`Default scenario name is returned`, () => { - window.history.pushState({}, 'Test', '/'); - expect(extractScenarioFromLocation(window.location)).toBe('default'); - }); - - test(`Throws error if user uses more than one scenario at a time`, () => { - window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); - expect(() => extractScenarioFromLocation(window.location)).toThrowError( - 'Only one scenario may be used at a time' - ); - }); - }); - - describe('Config', () => { - const scenarios: Scenarios = { - default: [ - { - url: /foo/, - method: 'GET', - response: { foo: 'GET' }, - responseCode: 200, - }, - ], - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - - injectMocks(scenarios, 'default', mockConfig); - expect(xhrSpy).toHaveBeenCalledTimes(2); - expect(xhrSpy).toHaveBeenCalledWith(proxy); - }); - - test('Does not set XHR proxy if allowXHRPassthrough is false', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - const mockConfig: MockConfig = { - allowXHRPassthrough: false, - }; - - injectMocks(scenarios, 'default', mockConfig); - expect(xhrSpy).toHaveBeenCalledTimes(1); - expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - }); - - test('Does not set XHR proxy if config is not passed', () => { - const xhrSpy = jest.spyOn(XHRMock, 'use'); - - injectMocks(scenarios, 'default'); - expect(xhrSpy).toHaveBeenCalledTimes(1); - expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - }); - - test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { - const mockConfig: MockConfig = { - allowFetchPassthrough: false, - }; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(false); - }); - - test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { - const mockConfig: MockConfig = {}; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(false); - }); - - test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { - const mockConfig: MockConfig = { - allowFetchPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - expect(fetchMock.config.fallbackToNetwork).toBe(true); - }); - - test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - const resGET = await axios.get('/foo'); - expect(resGET.data).toEqual({ foo: 'GET' }); - }); - - test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { - // We only expect an error in this case becaue the route does not exist. - // We just want to see if we attempted a real network request here. - const mockConfig: MockConfig = { - allowXHRPassthrough: true, - }; - injectMocks(scenarios, 'default', mockConfig); - try { - await axios.get('/bar'); - } catch (e) { - expect(e.message).toContain('Network Error'); - } - }); - }); + // describe('REST', () => { + // describe('HTTP methods', () => { + // const httpMethods: HttpMethod[] = [ + // 'GET', + // 'POST', + // 'PUT', + // 'PATCH', + // 'DELETE', + // ]; + + // httpMethods.forEach((httpMethod) => { + // const scenarios: Scenarios = { + // default: [ + // { + // url: /foo/, + // method: httpMethod, + // response: {}, + // responseCode: 200, + // }, + // { + // url: /bar/, + // method: httpMethod, + // response: {}, + // responseCode: 200, + // }, + // ], + // }; + + // test(`Mocks calls for ${httpMethod}`, () => { + // const fetchSpy = jest.spyOn( + // fetchMock, + // httpMethod.toLowerCase() as any + // ); + // const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); + + // injectMocks(scenarios, 'default'); + // expect(fetchSpy).toHaveBeenCalledTimes(2); + // expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); + // expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); + + // expect(xhrSpy).toHaveBeenCalledTimes(2); + // expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); + // expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); + // }); + // }); + // }); + + // describe('Fetch mocks', () => { + // const getMatcher = /get-endpoint/; + // const postMatcher = /post-endpoint/; + // const putMatcher = /put-endpoint/; + // const patchMatcher = /patch-endpoint/; + // const deleteMatcher = /delete-endpoint/; + + // const scenarios: Scenarios = { + // default: [ + // { + // url: getMatcher, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'foo' }, + // }, + // { + // url: postMatcher, + // method: 'POST', + // response: {}, + // responseCode: 200, + // responseHeaders: {}, + // }, + // { + // url: putMatcher, + // method: 'PUT', + // response: {}, + // responseCode: 200, + // responseHeaders: undefined, + // }, + // { + // url: patchMatcher, + // method: 'PATCH', + // response: {}, + // responseCode: 200, + // responseHeaders: undefined, + // }, + // { + // url: deleteMatcher, + // method: 'DELETE', + // response: {}, + // responseCode: 200, + // }, + // ], + // }; + + // beforeAll(() => { + // injectMocks(scenarios, 'default'); + // }); + + // test(`Correct endpoints being called for mocked fetch endpoints`, async () => { + // expect(fetchMock.calls().length).toEqual(0); + + // await fetch('/get-endpoint', { method: 'GET' }); + // expect(fetchMock.called(getMatcher)).toBeTruthy(); + + // await fetch('/post-endpoint', { method: 'POST' }); + // expect(fetchMock.called(postMatcher)).toBeTruthy(); + + // await fetch('/put-endpoint', { method: 'PUT' }); + // expect(fetchMock.called(putMatcher)).toBeTruthy(); + + // await fetch('/patch-endpoint', { method: 'PATCH' }); + // expect(fetchMock.called(patchMatcher)).toBeTruthy(); + + // await fetch('/delete-endpoint', { method: 'DELETE' }); + // expect(fetchMock.called(deleteMatcher)).toBeTruthy(); + // }); + // }); + + // describe('XHR mocks', () => { + // const scenarios: Scenarios = { + // default: [ + // { + // url: /foo/, + // method: 'GET', + // response: { foo: 'GET' }, + // responseCode: 200, + // responseHeaders: { token: 'foo' }, + // }, + // { + // url: /foo/, + // method: 'POST', + // response: { foo: 'POST' }, + // responseCode: 200, + // responseHeaders: {}, + // }, + // { + // url: /foo/, + // method: 'PUT', + // response: { foo: 'PUT' }, + // responseCode: 200, + // responseHeaders: undefined, + // }, + // { + // url: /foo/, + // method: 'PATCH', + // response: { foo: 'PATCH' }, + // responseCode: 200, + // responseHeaders: undefined, + // }, + // { + // url: /foo/, + // method: 'DELETE', + // response: { foo: 'DELETE' }, + // responseCode: 200, + // }, + // ], + // }; + + // beforeAll(() => { + // injectMocks(scenarios, 'default'); + // }); + + // test(`Correct response for mocked XHR endpoints`, async () => { + // const resGET = await axios.get('/foo'); + // expect(resGET.data).toEqual({ foo: 'GET' }); + // expect(resGET.headers).toEqual({ token: 'foo' }); + + // const resPOST = await axios.post('/foo'); + // expect(resPOST.data).toEqual({ foo: 'POST' }); + // expect(resPOST.headers).toEqual({}); + + // const resPUT = await axios.put('/foo'); + // expect(resPUT.data).toEqual({ foo: 'PUT' }); + // expect(resPUT.headers).toEqual({}); + + // const resPATCH = await axios.patch('/foo'); + // expect(resPATCH.data).toEqual({ foo: 'PATCH' }); + // expect(resPATCH.headers).toEqual({}); + + // const resDELETE = await axios.delete('/foo'); + // expect(resDELETE.data).toEqual({ foo: 'DELETE' }); + // expect(resDELETE.headers).toEqual({}); + // }); + // }); + // }); + + // describe('GraphQL mocks', () => { + // const graphQLMatcher = /graphql/; + // const scenarios: Scenarios = { + // default: [ + // { + // url: /graphql/, + // method: 'GRAPHQL', + // operations: [ + // { + // operationName: 'QueryTest', + // type: 'query', + // response: { data: { test: 'query test' } }, + // responseHeaders: { token: 'foo' }, + // }, + // { + // operationName: 'MutationTest', + // type: 'mutation', + // response: { data: { test: 'mutation test' } }, + // responseHeaders: { token: 'bar' }, + // }, + // ], + // }, + // ], + // }; + + // beforeAll(() => { + // injectMocks(scenarios, 'default'); + // }); + + // describe('Fetch', () => { + // test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { + // expect(fetchMock.calls().length).toEqual(0); + + // await fetch('https://www.test.com/graphql', { method: 'GET' }); + // expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); + // }); + + // test('Correct response when using GET request', async () => { + // const response = await fetch( + // 'https://www.test.com/graphql?query={}&operationName=QueryTest', + // { method: 'GET' } + // ); + // const responseBody = await response.json(); + // expect(responseBody.data).toEqual({ test: 'query test' }); + // expect(response.headers.get('token')).toEqual('foo'); + // }); + + // test('Correct response when using POST request', async () => { + // const response = await fetch('https://www.test.com/graphql', { + // method: 'POST', + // body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), + // }); + // const responseBody = await response.json(); + // expect(responseBody.data).toEqual({ test: 'mutation test' }); + // expect(response.headers.get('token')).toEqual('bar'); + // }); + // }); + + // describe('XHR', () => { + // test('Correct response when using GET request', async () => { + // const { data, headers } = await axios.get( + // 'https://www.test.com/graphql?query={}&operationName=QueryTest' + // ); + // expect(data.data).toEqual({ test: 'query test' }); + // expect(headers).toEqual({ token: 'foo' }); + // }); + + // test('Correct response when using POST request', async () => { + // const { data, headers } = await axios.post( + // 'https://www.test.com/graphql', + // { + // query: '{}', + // operationName: 'MutationTest', + // } + // ); + // expect(data.data).toEqual({ test: 'mutation test' }); + // expect(headers).toEqual({ token: 'bar' }); + // }); + // }); + // }); + + // describe('Scenarios', () => { + // const scenarios: Scenarios = { + // default: [ + // { + // url: /foo/, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'foo' }, + // }, + // { + // url: /bar/, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'bar' }, + // }, + // { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + // ], + // someScenario: [ + // { + // url: /bar/, + // method: 'GET', + // response: { some: 'otherResponse' }, + // responseCode: 401, + // }, + // { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + // ], + // }; + + // test(`Can extract the scenario from anywhere in the URL`, () => { + // window.history.pushState( + // {}, + // 'Test', + // '/?foo=bar&baz=bar&scenario=someScenario' + // ); + // const scenario = extractScenarioFromLocation(window.location); + // expect(scenario).toEqual('someScenario'); + // }); + + // test(`Can create a list for the default scenario`, () => { + // const result = reduceAllMocksForScenario(scenarios, 'default'); + + // expect(result).toEqual([ + // { + // url: /foo/, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'foo' }, + // }, + // { + // url: /bar/, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'bar' }, + // }, + // { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + // ]); + // }); + + // test(`Can create a list of mocks for a specific scenario`, () => { + // const result = reduceAllMocksForScenario(scenarios, 'someScenario'); + + // expect(result).toEqual([ + // { + // url: /foo/, + // method: 'GET', + // response: {}, + // responseCode: 200, + // responseHeaders: { token: 'foo' }, + // }, + // { + // url: /bar/, + // method: 'GET', + // response: { some: 'otherResponse' }, + // responseCode: 401, + // }, + // { + // url: /bar/, + // method: 'POST', + // response: {}, + // responseCode: 200, + // }, + // { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + // ]); + // }); + + // test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { + // const scenarios: Scenarios = { + // default: [ + // { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, + // ], + // scenario: [], + // }; + // const result = reduceAllMocksForScenario(scenarios, 'scenario'); + // expect(result).toEqual(scenarios.default); + // }); + + // test(`Returns empty array if default and scenario mocks are not defined`, () => { + // const scenarios: Scenarios = { + // default: [], + // scenario: [], + // }; + // const result = reduceAllMocksForScenario(scenarios, 'scenario'); + // expect(result).toEqual([]); + // }); + + // test(`Preserves any flags defined in the url regex`, () => { + // const scenarios: Scenarios = { + // default: [], + // scenario: [ + // { + // url: /^\/foo/i, + // method: 'GET', + // response: {}, + // responseCode: 200, + // }, + // { + // url: /^\/graphql/i, + // method: 'GRAPHQL', + // operations: [ + // { + // operationName: 'Query', + // type: 'query', + // response: { data: { test: 'data' } }, + // }, + // ], + // }, + // ], + // }; + // expect('/Foo').toMatch(scenarios.scenario[0].url); + // expect('/GraphQL').toMatch(scenarios.scenario[1].url); + + // const result = reduceAllMocksForScenario(scenarios, 'scenario'); + // expect(result).toHaveLength(2); + // expect('/Foo').toMatch(result[0].url); + // expect('/GraphQL').toMatch(result[1].url); + // }); + // }); + + // describe('Utility: extractScenarioFromLocation', () => { + // test(`Correct scenario name is returned`, () => { + // window.history.pushState({}, 'Test', '/?scenario=test'); + // expect(extractScenarioFromLocation(window.location)).toBe('test'); + // }); + + // test(`Default scenario name is returned`, () => { + // window.history.pushState({}, 'Test', '/'); + // expect(extractScenarioFromLocation(window.location)).toBe('default'); + // }); + + // test(`Throws error if user uses more than one scenario at a time`, () => { + // window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); + // expect(() => extractScenarioFromLocation(window.location)).toThrowError( + // 'Only one scenario may be used at a time' + // ); + // }); + // }); + + // describe('Config', () => { + // const scenarios: Scenarios = { + // default: [ + // { + // url: /foo/, + // method: 'GET', + // response: { foo: 'GET' }, + // responseCode: 200, + // }, + // ], + // }; + + // beforeEach(() => { + // jest.clearAllMocks(); + // }); + + // test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { + // const xhrSpy = jest.spyOn(XHRMock, 'use'); + + // const mockConfig: MockConfig = { + // allowXHRPassthrough: true, + // }; + + // injectMocks(scenarios, 'default', mockConfig); + // expect(xhrSpy).toHaveBeenCalledTimes(2); + // expect(xhrSpy).toHaveBeenCalledWith(proxy); + // }); + + // test('Does not set XHR proxy if allowXHRPassthrough is false', () => { + // const xhrSpy = jest.spyOn(XHRMock, 'use'); + + // const mockConfig: MockConfig = { + // allowXHRPassthrough: false, + // }; + + // injectMocks(scenarios, 'default', mockConfig); + // expect(xhrSpy).toHaveBeenCalledTimes(1); + // expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + // }); + + // test('Does not set XHR proxy if config is not passed', () => { + // const xhrSpy = jest.spyOn(XHRMock, 'use'); + + // injectMocks(scenarios, 'default'); + // expect(xhrSpy).toHaveBeenCalledTimes(1); + // expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + // }); + + // test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { + // const mockConfig: MockConfig = { + // allowFetchPassthrough: false, + // }; + // injectMocks(scenarios, 'default', mockConfig); + // expect(fetchMock.config.fallbackToNetwork).toBe(false); + // }); + + // test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { + // const mockConfig: MockConfig = {}; + // injectMocks(scenarios, 'default', mockConfig); + // expect(fetchMock.config.fallbackToNetwork).toBe(false); + // }); + + // test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { + // const mockConfig: MockConfig = { + // allowFetchPassthrough: true, + // }; + // injectMocks(scenarios, 'default', mockConfig); + // expect(fetchMock.config.fallbackToNetwork).toBe(true); + // }); + + // test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { + // const mockConfig: MockConfig = { + // allowXHRPassthrough: true, + // }; + // injectMocks(scenarios, 'default', mockConfig); + // const resGET = await axios.get('/foo'); + // expect(resGET.data).toEqual({ foo: 'GET' }); + // }); + + // test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { + // // We only expect an error in this case becaue the route does not exist. + // // We just want to see if we attempted a real network request here. + // const mockConfig: MockConfig = { + // allowXHRPassthrough: true, + // }; + // injectMocks(scenarios, 'default', mockConfig); + // try { + // await axios.get('/bar'); + // } catch (e) { + // expect(e.message).toContain('Network Error'); + // } + // }); + // }); }); const createServer = () => { const testURI = 'ws://localhost/foo'; - const testServerMock: WebSocketServerMock = (server) => () => { + const testServerMock = (server: MockSever) => (WebSocketServerMock) => { server.on('connection', (socket) => { socket.on('message', (data) => { socket.send(data.toString()); @@ -579,7 +604,7 @@ const createServer = () => { }); return server; }; - return testServerMock(new MockSever(testURI))(); + return testServerMock(new MockSever(testURI)); }; const awaitSocket = (socket, state) => { diff --git a/src/mocks.ts b/src/mocks.ts index 3c47a91..672825d 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -8,6 +8,7 @@ import { HttpMock, GraphQLMock, Operation, + WebSocketMock, } from './types'; /** @@ -44,11 +45,17 @@ export const injectMocks = ( throw new Error('Unable to instantiate mocks'); } - const restMocks = mocks.filter((m) => m.method !== 'GRAPHQL') as HttpMock[]; + const restMocks = mocks.filter( + (m) => !['GRAPHQL', 'WEBSOCKET'].includes(m.method) + ) as HttpMock[]; const graphQLMocks = mocks.filter( (m) => m.method === 'GRAPHQL' ) as GraphQLMock[]; + const webSocketMocks = mocks.filter( + (m) => m.method === 'WEBSOCKET' + ) as WebSocketMock[]; + restMocks.forEach(handleRestMock); graphQLMocks.forEach(handleGraphQLMock); diff --git a/src/types.ts b/src/types.ts index 5b25dc0..387f952 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,7 @@ export type GraphQLMock = { operations: Array; }; -export type WebSocketServerMock = (mockServer: MockServer) => () => MockServer; +export type WebSocketServerMock = (mockServer: MockServer) => void; export type WebSocketMock = { url: RegExp; method: 'WEBSOCKET'; From e12081c1e4912c6a46fbb51fae92767d69abd60f Mon Sep 17 00:00:00 2001 From: Aiden Date: Mon, 13 Sep 2021 18:51:55 +0100 Subject: [PATCH 05/15] implement mock server --- src/mocks.test.ts | 1137 ++++++++++++++++++++++----------------------- src/mocks.ts | 8 +- src/types.ts | 2 +- 3 files changed, 570 insertions(+), 577 deletions(-) diff --git a/src/mocks.test.ts b/src/mocks.test.ts index 15d149e..02b67a6 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -1,19 +1,14 @@ import axios from 'axios'; import fetchMock from 'fetch-mock'; -// import 'isomorphic-fetch'; -import { Server as MockSever, WebSocket as ServerWebsocket } from 'mock-socket'; -// import XHRMock, { proxy } from 'xhr-mock'; +import 'isomorphic-fetch'; +import { Server as MockSever } from 'mock-socket'; +import XHRMock, { proxy } from 'xhr-mock'; import { extractScenarioFromLocation, injectMocks, reduceAllMocksForScenario, } from './mocks'; -import { - HttpMethod, - MockConfig, - Scenarios, - WebSocketServerMock, -} from './types'; +import { HttpMethod, MockConfig, Scenarios } from './types'; describe('data-mocks', () => { beforeEach(() => { @@ -21,582 +16,574 @@ describe('data-mocks', () => { }); describe('Websockets', () => { - let server: WebSocketServerMock; - beforeAll(async () => { - server = await createServer(); + const testURL = 'ws://localhost/foo'; + it('Spawns a working websocket server', async () => { + const onMessage = jest.fn(); + const onConnect = jest.fn(); + const scenarios: Scenarios = { + default: [ + { + url: testURL, + method: 'WEBSOCKET', + server: (s) => { + s.on('connection', (socket) => { + console.log('conneted'); + onConnect(); + socket.on('message', (req) => { + onMessage(); + socket.send(req.toString()); + s.close(); + }); + }); + }, + }, + ], + }; + injectMocks(scenarios, 'default'); + + const socket = new WebSocket(testURL); + let res; + socket.addEventListener('message', (data) => { + res = data.data; + socket.close(); + }); + + await awaitSocket(socket, WebSocket.OPEN); + expect(onConnect).toBeCalled(); + + const message = 'hello world'; + socket.send(message); + await awaitSocket(socket, WebSocket.CLOSED); + expect(onMessage).toBeCalled(); + + expect(res).toEqual(message); + }); + }); + + describe('REST', () => { + describe('HTTP methods', () => { + const httpMethods: HttpMethod[] = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + ]; + + httpMethods.forEach((httpMethod) => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: httpMethod, + response: {}, + responseCode: 200, + }, + { + url: /bar/, + method: httpMethod, + response: {}, + responseCode: 200, + }, + ], + }; + + test(`Mocks calls for ${httpMethod}`, () => { + const fetchSpy = jest.spyOn( + fetchMock, + httpMethod.toLowerCase() as any + ); + const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); + + injectMocks(scenarios, 'default'); + expect(fetchSpy).toHaveBeenCalledTimes(2); + expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); + expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); + + expect(xhrSpy).toHaveBeenCalledTimes(2); + expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); + expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); + }); + }); + }); + + describe('Fetch mocks', () => { + const getMatcher = /get-endpoint/; + const postMatcher = /post-endpoint/; + const putMatcher = /put-endpoint/; + const patchMatcher = /patch-endpoint/; + const deleteMatcher = /delete-endpoint/; + + const scenarios: Scenarios = { + default: [ + { + url: getMatcher, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: postMatcher, + method: 'POST', + response: {}, + responseCode: 200, + responseHeaders: {}, + }, + { + url: putMatcher, + method: 'PUT', + response: {}, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: patchMatcher, + method: 'PATCH', + response: {}, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: deleteMatcher, + method: 'DELETE', + response: {}, + responseCode: 200, + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); + }); + + test(`Correct endpoints being called for mocked fetch endpoints`, async () => { + expect(fetchMock.calls().length).toEqual(0); + + await fetch('/get-endpoint', { method: 'GET' }); + expect(fetchMock.called(getMatcher)).toBeTruthy(); + + await fetch('/post-endpoint', { method: 'POST' }); + expect(fetchMock.called(postMatcher)).toBeTruthy(); + + await fetch('/put-endpoint', { method: 'PUT' }); + expect(fetchMock.called(putMatcher)).toBeTruthy(); + + await fetch('/patch-endpoint', { method: 'PATCH' }); + expect(fetchMock.called(patchMatcher)).toBeTruthy(); + + await fetch('/delete-endpoint', { method: 'DELETE' }); + expect(fetchMock.called(deleteMatcher)).toBeTruthy(); + }); + }); + + describe('XHR mocks', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: { foo: 'GET' }, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /foo/, + method: 'POST', + response: { foo: 'POST' }, + responseCode: 200, + responseHeaders: {}, + }, + { + url: /foo/, + method: 'PUT', + response: { foo: 'PUT' }, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: /foo/, + method: 'PATCH', + response: { foo: 'PATCH' }, + responseCode: 200, + responseHeaders: undefined, + }, + { + url: /foo/, + method: 'DELETE', + response: { foo: 'DELETE' }, + responseCode: 200, + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); + }); + + test(`Correct response for mocked XHR endpoints`, async () => { + const resGET = await axios.get('/foo'); + expect(resGET.data).toEqual({ foo: 'GET' }); + expect(resGET.headers).toEqual({ token: 'foo' }); + + const resPOST = await axios.post('/foo'); + expect(resPOST.data).toEqual({ foo: 'POST' }); + expect(resPOST.headers).toEqual({}); + + const resPUT = await axios.put('/foo'); + expect(resPUT.data).toEqual({ foo: 'PUT' }); + expect(resPUT.headers).toEqual({}); + + const resPATCH = await axios.patch('/foo'); + expect(resPATCH.data).toEqual({ foo: 'PATCH' }); + expect(resPATCH.headers).toEqual({}); + + const resDELETE = await axios.delete('/foo'); + expect(resDELETE.data).toEqual({ foo: 'DELETE' }); + expect(resDELETE.headers).toEqual({}); + }); + }); + }); + + describe('GraphQL mocks', () => { + const graphQLMatcher = /graphql/; + const scenarios: Scenarios = { + default: [ + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'QueryTest', + type: 'query', + response: { data: { test: 'query test' } }, + responseHeaders: { token: 'foo' }, + }, + { + operationName: 'MutationTest', + type: 'mutation', + response: { data: { test: 'mutation test' } }, + responseHeaders: { token: 'bar' }, + }, + ], + }, + ], + }; + + beforeAll(() => { + injectMocks(scenarios, 'default'); }); - describe('Websocket server', () => { - test('WS echo server can be mocked and is responsive', async () => { - const testURI = 'ws://localhost/foo'; - const socket = new WebSocket(testURI); - await awaitSocket(socket, socket.OPEN); - let res; - socket.addEventListener('message', (data) => { - res = data.data; - socket.close(); + describe('Fetch', () => { + test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { + expect(fetchMock.calls().length).toEqual(0); + + await fetch('https://www.test.com/graphql', { method: 'GET' }); + expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); + }); + + test('Correct response when using GET request', async () => { + const response = await fetch( + 'https://www.test.com/graphql?query={}&operationName=QueryTest', + { method: 'GET' } + ); + const responseBody = await response.json(); + expect(responseBody.data).toEqual({ test: 'query test' }); + expect(response.headers.get('token')).toEqual('foo'); + }); + + test('Correct response when using POST request', async () => { + const response = await fetch('https://www.test.com/graphql', { + method: 'POST', + body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), }); + const responseBody = await response.json(); + expect(responseBody.data).toEqual({ test: 'mutation test' }); + expect(response.headers.get('token')).toEqual('bar'); + }); + }); - const message = 'hello'; - socket.send(message); + describe('XHR', () => { + test('Correct response when using GET request', async () => { + const { data, headers } = await axios.get( + 'https://www.test.com/graphql?query={}&operationName=QueryTest' + ); + expect(data.data).toEqual({ test: 'query test' }); + expect(headers).toEqual({ token: 'foo' }); + }); - await awaitSocket(socket, socket.CLOSED); - return expect(res).toBe(message); + test('Correct response when using POST request', async () => { + const { data, headers } = await axios.post( + 'https://www.test.com/graphql', + { + query: '{}', + operationName: 'MutationTest', + } + ); + expect(data.data).toEqual({ test: 'mutation test' }); + expect(headers).toEqual({ token: 'bar' }); }); }); - // describe('Mock from scenario', () => { - // it('works', async () => { - // const onMessage = jest.fn() - // const onConnect = (socket: ServerWebsocket) => socket.on('message', onMessage) - // const onConnectSpy = jest.spyOn({ onConnect }, 'onConnect') - // const onMessageSpy = () => jest.spyOn({ onMessage }, 'onMessage') - - // const scenarios: Scenarios = { - // default: [ - // { - // url: /foo/, - // method: 'WEBSOCKET', - // server: (s) => { - // s.on('connection', onConnect); - // } - // } - // ], - // }; - // injectMocks(scenarios, 'default'); - // const socket = new WebSocket('/foo') - // await awaitSocket(socket, WebSocket.OPEN) - // expect(onConnectSpy).toBeCalled() - - // }) - - // }) }); - // describe('REST', () => { - // describe('HTTP methods', () => { - // const httpMethods: HttpMethod[] = [ - // 'GET', - // 'POST', - // 'PUT', - // 'PATCH', - // 'DELETE', - // ]; - - // httpMethods.forEach((httpMethod) => { - // const scenarios: Scenarios = { - // default: [ - // { - // url: /foo/, - // method: httpMethod, - // response: {}, - // responseCode: 200, - // }, - // { - // url: /bar/, - // method: httpMethod, - // response: {}, - // responseCode: 200, - // }, - // ], - // }; - - // test(`Mocks calls for ${httpMethod}`, () => { - // const fetchSpy = jest.spyOn( - // fetchMock, - // httpMethod.toLowerCase() as any - // ); - // const xhrSpy = jest.spyOn(XHRMock, httpMethod.toLowerCase() as any); - - // injectMocks(scenarios, 'default'); - // expect(fetchSpy).toHaveBeenCalledTimes(2); - // expect(fetchSpy.mock.calls[0][0]).toEqual(/foo/); - // expect(fetchSpy.mock.calls[1][0]).toEqual(/bar/); - - // expect(xhrSpy).toHaveBeenCalledTimes(2); - // expect(xhrSpy.mock.calls[0][0]).toEqual(/foo/); - // expect(xhrSpy.mock.calls[1][0]).toEqual(/bar/); - // }); - // }); - // }); - - // describe('Fetch mocks', () => { - // const getMatcher = /get-endpoint/; - // const postMatcher = /post-endpoint/; - // const putMatcher = /put-endpoint/; - // const patchMatcher = /patch-endpoint/; - // const deleteMatcher = /delete-endpoint/; - - // const scenarios: Scenarios = { - // default: [ - // { - // url: getMatcher, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'foo' }, - // }, - // { - // url: postMatcher, - // method: 'POST', - // response: {}, - // responseCode: 200, - // responseHeaders: {}, - // }, - // { - // url: putMatcher, - // method: 'PUT', - // response: {}, - // responseCode: 200, - // responseHeaders: undefined, - // }, - // { - // url: patchMatcher, - // method: 'PATCH', - // response: {}, - // responseCode: 200, - // responseHeaders: undefined, - // }, - // { - // url: deleteMatcher, - // method: 'DELETE', - // response: {}, - // responseCode: 200, - // }, - // ], - // }; - - // beforeAll(() => { - // injectMocks(scenarios, 'default'); - // }); - - // test(`Correct endpoints being called for mocked fetch endpoints`, async () => { - // expect(fetchMock.calls().length).toEqual(0); - - // await fetch('/get-endpoint', { method: 'GET' }); - // expect(fetchMock.called(getMatcher)).toBeTruthy(); - - // await fetch('/post-endpoint', { method: 'POST' }); - // expect(fetchMock.called(postMatcher)).toBeTruthy(); - - // await fetch('/put-endpoint', { method: 'PUT' }); - // expect(fetchMock.called(putMatcher)).toBeTruthy(); - - // await fetch('/patch-endpoint', { method: 'PATCH' }); - // expect(fetchMock.called(patchMatcher)).toBeTruthy(); - - // await fetch('/delete-endpoint', { method: 'DELETE' }); - // expect(fetchMock.called(deleteMatcher)).toBeTruthy(); - // }); - // }); - - // describe('XHR mocks', () => { - // const scenarios: Scenarios = { - // default: [ - // { - // url: /foo/, - // method: 'GET', - // response: { foo: 'GET' }, - // responseCode: 200, - // responseHeaders: { token: 'foo' }, - // }, - // { - // url: /foo/, - // method: 'POST', - // response: { foo: 'POST' }, - // responseCode: 200, - // responseHeaders: {}, - // }, - // { - // url: /foo/, - // method: 'PUT', - // response: { foo: 'PUT' }, - // responseCode: 200, - // responseHeaders: undefined, - // }, - // { - // url: /foo/, - // method: 'PATCH', - // response: { foo: 'PATCH' }, - // responseCode: 200, - // responseHeaders: undefined, - // }, - // { - // url: /foo/, - // method: 'DELETE', - // response: { foo: 'DELETE' }, - // responseCode: 200, - // }, - // ], - // }; - - // beforeAll(() => { - // injectMocks(scenarios, 'default'); - // }); - - // test(`Correct response for mocked XHR endpoints`, async () => { - // const resGET = await axios.get('/foo'); - // expect(resGET.data).toEqual({ foo: 'GET' }); - // expect(resGET.headers).toEqual({ token: 'foo' }); - - // const resPOST = await axios.post('/foo'); - // expect(resPOST.data).toEqual({ foo: 'POST' }); - // expect(resPOST.headers).toEqual({}); - - // const resPUT = await axios.put('/foo'); - // expect(resPUT.data).toEqual({ foo: 'PUT' }); - // expect(resPUT.headers).toEqual({}); - - // const resPATCH = await axios.patch('/foo'); - // expect(resPATCH.data).toEqual({ foo: 'PATCH' }); - // expect(resPATCH.headers).toEqual({}); - - // const resDELETE = await axios.delete('/foo'); - // expect(resDELETE.data).toEqual({ foo: 'DELETE' }); - // expect(resDELETE.headers).toEqual({}); - // }); - // }); - // }); - - // describe('GraphQL mocks', () => { - // const graphQLMatcher = /graphql/; - // const scenarios: Scenarios = { - // default: [ - // { - // url: /graphql/, - // method: 'GRAPHQL', - // operations: [ - // { - // operationName: 'QueryTest', - // type: 'query', - // response: { data: { test: 'query test' } }, - // responseHeaders: { token: 'foo' }, - // }, - // { - // operationName: 'MutationTest', - // type: 'mutation', - // response: { data: { test: 'mutation test' } }, - // responseHeaders: { token: 'bar' }, - // }, - // ], - // }, - // ], - // }; - - // beforeAll(() => { - // injectMocks(scenarios, 'default'); - // }); - - // describe('Fetch', () => { - // test(`Correct endpoints being called for mocked graphQL endpoints`, async () => { - // expect(fetchMock.calls().length).toEqual(0); - - // await fetch('https://www.test.com/graphql', { method: 'GET' }); - // expect(fetchMock.called(graphQLMatcher)).toBeTruthy(); - // }); - - // test('Correct response when using GET request', async () => { - // const response = await fetch( - // 'https://www.test.com/graphql?query={}&operationName=QueryTest', - // { method: 'GET' } - // ); - // const responseBody = await response.json(); - // expect(responseBody.data).toEqual({ test: 'query test' }); - // expect(response.headers.get('token')).toEqual('foo'); - // }); - - // test('Correct response when using POST request', async () => { - // const response = await fetch('https://www.test.com/graphql', { - // method: 'POST', - // body: JSON.stringify({ query: '{}', operationName: 'MutationTest' }), - // }); - // const responseBody = await response.json(); - // expect(responseBody.data).toEqual({ test: 'mutation test' }); - // expect(response.headers.get('token')).toEqual('bar'); - // }); - // }); - - // describe('XHR', () => { - // test('Correct response when using GET request', async () => { - // const { data, headers } = await axios.get( - // 'https://www.test.com/graphql?query={}&operationName=QueryTest' - // ); - // expect(data.data).toEqual({ test: 'query test' }); - // expect(headers).toEqual({ token: 'foo' }); - // }); - - // test('Correct response when using POST request', async () => { - // const { data, headers } = await axios.post( - // 'https://www.test.com/graphql', - // { - // query: '{}', - // operationName: 'MutationTest', - // } - // ); - // expect(data.data).toEqual({ test: 'mutation test' }); - // expect(headers).toEqual({ token: 'bar' }); - // }); - // }); - // }); - - // describe('Scenarios', () => { - // const scenarios: Scenarios = { - // default: [ - // { - // url: /foo/, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'foo' }, - // }, - // { - // url: /bar/, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'bar' }, - // }, - // { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - // ], - // someScenario: [ - // { - // url: /bar/, - // method: 'GET', - // response: { some: 'otherResponse' }, - // responseCode: 401, - // }, - // { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - // ], - // }; - - // test(`Can extract the scenario from anywhere in the URL`, () => { - // window.history.pushState( - // {}, - // 'Test', - // '/?foo=bar&baz=bar&scenario=someScenario' - // ); - // const scenario = extractScenarioFromLocation(window.location); - // expect(scenario).toEqual('someScenario'); - // }); - - // test(`Can create a list for the default scenario`, () => { - // const result = reduceAllMocksForScenario(scenarios, 'default'); - - // expect(result).toEqual([ - // { - // url: /foo/, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'foo' }, - // }, - // { - // url: /bar/, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'bar' }, - // }, - // { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, - // ]); - // }); - - // test(`Can create a list of mocks for a specific scenario`, () => { - // const result = reduceAllMocksForScenario(scenarios, 'someScenario'); - - // expect(result).toEqual([ - // { - // url: /foo/, - // method: 'GET', - // response: {}, - // responseCode: 200, - // responseHeaders: { token: 'foo' }, - // }, - // { - // url: /bar/, - // method: 'GET', - // response: { some: 'otherResponse' }, - // responseCode: 401, - // }, - // { - // url: /bar/, - // method: 'POST', - // response: {}, - // responseCode: 200, - // }, - // { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, - // ]); - // }); - - // test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { - // const scenarios: Scenarios = { - // default: [ - // { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, - // ], - // scenario: [], - // }; - // const result = reduceAllMocksForScenario(scenarios, 'scenario'); - // expect(result).toEqual(scenarios.default); - // }); - - // test(`Returns empty array if default and scenario mocks are not defined`, () => { - // const scenarios: Scenarios = { - // default: [], - // scenario: [], - // }; - // const result = reduceAllMocksForScenario(scenarios, 'scenario'); - // expect(result).toEqual([]); - // }); - - // test(`Preserves any flags defined in the url regex`, () => { - // const scenarios: Scenarios = { - // default: [], - // scenario: [ - // { - // url: /^\/foo/i, - // method: 'GET', - // response: {}, - // responseCode: 200, - // }, - // { - // url: /^\/graphql/i, - // method: 'GRAPHQL', - // operations: [ - // { - // operationName: 'Query', - // type: 'query', - // response: { data: { test: 'data' } }, - // }, - // ], - // }, - // ], - // }; - // expect('/Foo').toMatch(scenarios.scenario[0].url); - // expect('/GraphQL').toMatch(scenarios.scenario[1].url); - - // const result = reduceAllMocksForScenario(scenarios, 'scenario'); - // expect(result).toHaveLength(2); - // expect('/Foo').toMatch(result[0].url); - // expect('/GraphQL').toMatch(result[1].url); - // }); - // }); - - // describe('Utility: extractScenarioFromLocation', () => { - // test(`Correct scenario name is returned`, () => { - // window.history.pushState({}, 'Test', '/?scenario=test'); - // expect(extractScenarioFromLocation(window.location)).toBe('test'); - // }); - - // test(`Default scenario name is returned`, () => { - // window.history.pushState({}, 'Test', '/'); - // expect(extractScenarioFromLocation(window.location)).toBe('default'); - // }); - - // test(`Throws error if user uses more than one scenario at a time`, () => { - // window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); - // expect(() => extractScenarioFromLocation(window.location)).toThrowError( - // 'Only one scenario may be used at a time' - // ); - // }); - // }); - - // describe('Config', () => { - // const scenarios: Scenarios = { - // default: [ - // { - // url: /foo/, - // method: 'GET', - // response: { foo: 'GET' }, - // responseCode: 200, - // }, - // ], - // }; - - // beforeEach(() => { - // jest.clearAllMocks(); - // }); - - // test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { - // const xhrSpy = jest.spyOn(XHRMock, 'use'); - - // const mockConfig: MockConfig = { - // allowXHRPassthrough: true, - // }; - - // injectMocks(scenarios, 'default', mockConfig); - // expect(xhrSpy).toHaveBeenCalledTimes(2); - // expect(xhrSpy).toHaveBeenCalledWith(proxy); - // }); - - // test('Does not set XHR proxy if allowXHRPassthrough is false', () => { - // const xhrSpy = jest.spyOn(XHRMock, 'use'); - - // const mockConfig: MockConfig = { - // allowXHRPassthrough: false, - // }; - - // injectMocks(scenarios, 'default', mockConfig); - // expect(xhrSpy).toHaveBeenCalledTimes(1); - // expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - // }); - - // test('Does not set XHR proxy if config is not passed', () => { - // const xhrSpy = jest.spyOn(XHRMock, 'use'); - - // injectMocks(scenarios, 'default'); - // expect(xhrSpy).toHaveBeenCalledTimes(1); - // expect(xhrSpy).not.toHaveBeenCalledWith(proxy); - // }); - - // test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { - // const mockConfig: MockConfig = { - // allowFetchPassthrough: false, - // }; - // injectMocks(scenarios, 'default', mockConfig); - // expect(fetchMock.config.fallbackToNetwork).toBe(false); - // }); - - // test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { - // const mockConfig: MockConfig = {}; - // injectMocks(scenarios, 'default', mockConfig); - // expect(fetchMock.config.fallbackToNetwork).toBe(false); - // }); - - // test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { - // const mockConfig: MockConfig = { - // allowFetchPassthrough: true, - // }; - // injectMocks(scenarios, 'default', mockConfig); - // expect(fetchMock.config.fallbackToNetwork).toBe(true); - // }); - - // test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { - // const mockConfig: MockConfig = { - // allowXHRPassthrough: true, - // }; - // injectMocks(scenarios, 'default', mockConfig); - // const resGET = await axios.get('/foo'); - // expect(resGET.data).toEqual({ foo: 'GET' }); - // }); - - // test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { - // // We only expect an error in this case becaue the route does not exist. - // // We just want to see if we attempted a real network request here. - // const mockConfig: MockConfig = { - // allowXHRPassthrough: true, - // }; - // injectMocks(scenarios, 'default', mockConfig); - // try { - // await axios.get('/bar'); - // } catch (e) { - // expect(e.message).toContain('Network Error'); - // } - // }); - // }); + describe('Scenarios', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'bar' }, + }, + { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + ], + someScenario: [ + { + url: /bar/, + method: 'GET', + response: { some: 'otherResponse' }, + responseCode: 401, + }, + { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + ], + }; + + test(`Can extract the scenario from anywhere in the URL`, () => { + window.history.pushState( + {}, + 'Test', + '/?foo=bar&baz=bar&scenario=someScenario' + ); + const scenario = extractScenarioFromLocation(window.location); + expect(scenario).toEqual('someScenario'); + }); + + test(`Can create a list for the default scenario`, () => { + const result = reduceAllMocksForScenario(scenarios, 'default'); + + expect(result).toEqual([ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'bar' }, + }, + { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + ]); + }); + + test(`Can create a list of mocks for a specific scenario`, () => { + const result = reduceAllMocksForScenario(scenarios, 'someScenario'); + + expect(result).toEqual([ + { + url: /foo/, + method: 'GET', + response: {}, + responseCode: 200, + responseHeaders: { token: 'foo' }, + }, + { + url: /bar/, + method: 'GET', + response: { some: 'otherResponse' }, + responseCode: 401, + }, + { + url: /bar/, + method: 'POST', + response: {}, + responseCode: 200, + }, + { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + ]); + }); + + test(`Returns default mocks if user specifies scenario with no defined mocks`, () => { + const scenarios: Scenarios = { + default: [ + { url: /foo/, method: 'GET', response: {}, responseCode: 200 }, + ], + scenario: [], + }; + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toEqual(scenarios.default); + }); + + test(`Returns empty array if default and scenario mocks are not defined`, () => { + const scenarios: Scenarios = { + default: [], + scenario: [], + }; + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toEqual([]); + }); + + test(`Preserves any flags defined in the url regex`, () => { + const scenarios: Scenarios = { + default: [], + scenario: [ + { + url: /^\/foo/i, + method: 'GET', + response: {}, + responseCode: 200, + }, + { + url: /^\/graphql/i, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'data' } }, + }, + ], + }, + ], + }; + expect('/Foo').toMatch(scenarios.scenario[0].url); + expect('/GraphQL').toMatch(scenarios.scenario[1].url); + + const result = reduceAllMocksForScenario(scenarios, 'scenario'); + expect(result).toHaveLength(2); + expect('/Foo').toMatch(result[0].url); + expect('/GraphQL').toMatch(result[1].url); + }); + }); + + describe('Utility: extractScenarioFromLocation', () => { + test(`Correct scenario name is returned`, () => { + window.history.pushState({}, 'Test', '/?scenario=test'); + expect(extractScenarioFromLocation(window.location)).toBe('test'); + }); + + test(`Default scenario name is returned`, () => { + window.history.pushState({}, 'Test', '/'); + expect(extractScenarioFromLocation(window.location)).toBe('default'); + }); + + test(`Throws error if user uses more than one scenario at a time`, () => { + window.history.pushState({}, 'Test', '/?scenario=test&scenario=foo'); + expect(() => extractScenarioFromLocation(window.location)).toThrowError( + 'Only one scenario may be used at a time' + ); + }); + }); + + describe('Config', () => { + const scenarios: Scenarios = { + default: [ + { + url: /foo/, + method: 'GET', + response: { foo: 'GET' }, + responseCode: 200, + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('Sets XHR proxy if allowXHRPassthrough is set in config', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + + injectMocks(scenarios, 'default', mockConfig); + expect(xhrSpy).toHaveBeenCalledTimes(2); + expect(xhrSpy).toHaveBeenCalledWith(proxy); + }); + + test('Does not set XHR proxy if allowXHRPassthrough is false', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + const mockConfig: MockConfig = { + allowXHRPassthrough: false, + }; + + injectMocks(scenarios, 'default', mockConfig); + expect(xhrSpy).toHaveBeenCalledTimes(1); + expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + }); + + test('Does not set XHR proxy if config is not passed', () => { + const xhrSpy = jest.spyOn(XHRMock, 'use'); + + injectMocks(scenarios, 'default'); + expect(xhrSpy).toHaveBeenCalledTimes(1); + expect(xhrSpy).not.toHaveBeenCalledWith(proxy); + }); + + test('Sets fallbackToNetwork to false if allowFetchPassthrough is set to false in config', () => { + const mockConfig: MockConfig = { + allowFetchPassthrough: false, + }; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(false); + }); + + test('Sets fallbackToNetwork to false if allowFetchPassthrough is not passed in', () => { + const mockConfig: MockConfig = {}; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(false); + }); + + test('Sets fallbackToNetwork to true if allowFetchPassthrough is set to true in config', () => { + const mockConfig: MockConfig = { + allowFetchPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + expect(fetchMock.config.fallbackToNetwork).toBe(true); + }); + + test('Returns mock data if allowXHRPassthrough is set to true and route exists as mock', async () => { + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + const resGET = await axios.get('/foo'); + expect(resGET.data).toEqual({ foo: 'GET' }); + }); + + test('Returns error if allowXHRPassthrough is set to true and route does not exists as mock', async () => { + // We only expect an error in this case becaue the route does not exist. + // We just want to see if we attempted a real network request here. + const mockConfig: MockConfig = { + allowXHRPassthrough: true, + }; + injectMocks(scenarios, 'default', mockConfig); + try { + await axios.get('/bar'); + } catch (e) { + expect(e.message).toContain('Network Error'); + } + }); + }); }); -const createServer = () => { - const testURI = 'ws://localhost/foo'; - const testServerMock = (server: MockSever) => (WebSocketServerMock) => { +const createServer = (url: string) => { + const testServerMock = (server: MockSever) => { server.on('connection', (socket) => { socket.on('message', (data) => { socket.send(data.toString()); @@ -604,7 +591,7 @@ const createServer = () => { }); return server; }; - return testServerMock(new MockSever(testURI)); + return testServerMock(new MockSever(url)); }; const awaitSocket = (socket, state) => { diff --git a/src/mocks.ts b/src/mocks.ts index 672825d..95ad40b 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -1,6 +1,7 @@ import fetchMock from 'fetch-mock'; import XHRMock, { delay as xhrMockDelay, proxy } from 'xhr-mock'; import { parse } from 'query-string'; +import { Server as MockServer } from 'mock-socket'; import { Scenarios, MockConfig, @@ -58,6 +59,7 @@ export const injectMocks = ( restMocks.forEach(handleRestMock); graphQLMocks.forEach(handleGraphQLMock); + webSocketMocks.forEach(handleWebsocketMock); if (config?.allowXHRPassthrough) { XHRMock.use(proxy); @@ -156,7 +158,6 @@ function handleRestMock({ status: responseCode, headers: responseHeaders, }; - switch (method) { case 'GET': fetchMock.get(url, () => addDelay(delay).then(() => finalResponse), { @@ -287,6 +288,11 @@ function handleGraphQLMock({ url, operations }: GraphQLMock) { }); } +const handleWebsocketMock = ({ url, server }: WebSocketMock) => { + console.log('makeing, ', url); + server(new MockServer(url)); +}; + /** * Adds delay (in ms) before resolving a promise. */ diff --git a/src/types.ts b/src/types.ts index 387f952..2c48dde 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,7 +30,7 @@ export type GraphQLMock = { export type WebSocketServerMock = (mockServer: MockServer) => void; export type WebSocketMock = { - url: RegExp; + url: string; method: 'WEBSOCKET'; server: WebSocketServerMock; }; From 743b55c54c13890bd471e7c14870c7c7ab3d28db Mon Sep 17 00:00:00 2001 From: Aiden Date: Mon, 13 Sep 2021 19:47:54 +0100 Subject: [PATCH 06/15] test reduction --- src/mocks.test.ts | 70 +++++++++++++++++++++++++++++++++++++++-------- src/mocks.ts | 18 ++++++++++-- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/mocks.test.ts b/src/mocks.test.ts index 02b67a6..42aa9d7 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -327,6 +327,8 @@ describe('data-mocks', () => { }); describe('Scenarios', () => { + const websocketServerFn = jest.fn(); + const anotherServerFn = jest.fn(); const scenarios: Scenarios = { default: [ { @@ -344,6 +346,22 @@ describe('data-mocks', () => { responseHeaders: { token: 'bar' }, }, { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'data' } }, + }, + ], + }, + { + url: 'ws://localhost', + method: 'WEBSOCKET', + server: websocketServerFn, + }, ], someScenario: [ { @@ -353,6 +371,18 @@ describe('data-mocks', () => { responseCode: 401, }, { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'different data' } }, + }, + ], + }, + { url: 'ws://localhost', method: 'WEBSOCKET', server: anotherServerFn }, ], }; @@ -385,6 +415,22 @@ describe('data-mocks', () => { responseHeaders: { token: 'bar' }, }, { url: /bar/, method: 'POST', response: {}, responseCode: 200 }, + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'data' } }, + }, + ], + }, + { + url: 'ws://localhost', + method: 'WEBSOCKET', + server: websocketServerFn, + }, ]); }); @@ -412,6 +458,18 @@ describe('data-mocks', () => { responseCode: 200, }, { url: /baz/, method: 'POST', response: {}, responseCode: 200 }, + { + url: /graphql/, + method: 'GRAPHQL', + operations: [ + { + operationName: 'Query', + type: 'query', + response: { data: { test: 'different data' } }, + }, + ], + }, + { url: 'ws://localhost', method: 'WEBSOCKET', server: anotherServerFn }, ]); }); @@ -582,18 +640,6 @@ describe('data-mocks', () => { }); }); -const createServer = (url: string) => { - const testServerMock = (server: MockSever) => { - server.on('connection', (socket) => { - socket.on('message', (data) => { - socket.send(data.toString()); - }); - }); - return server; - }; - return testServerMock(new MockSever(url)); -}; - const awaitSocket = (socket, state) => { return new Promise(function (resolve) { setTimeout(function () { diff --git a/src/mocks.ts b/src/mocks.ts index 95ad40b..f2e88bb 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -88,12 +88,24 @@ export const reduceAllMocksForScenario = ( const mocks = defaultMocks.concat(scenarioMocks); const initialHttpMocks = mocks.filter( - ({ method }) => method !== 'GRAPHQL' + (m) => !['GRAPHQL', 'WEBSOCKET'].includes(m.method) ) as HttpMock[]; const initialGraphQlMocks = mocks.filter( ({ method }) => method === 'GRAPHQL' ) as GraphQLMock[]; + const initialWebsocketMocks = mocks.filter( + (m) => m.method === 'WEBSOCKET' + ) as WebSocketMock[]; + + const websocketMocksByUrl = initialWebsocketMocks.reduce< + Record + >((result, mock) => { + const { url } = mock; + result[url] = mock; + return result; + }, {}); + const httpMocksByUrlAndMethod = initialHttpMocks.reduce< Record >((result, mock) => { @@ -139,7 +151,9 @@ export const reduceAllMocksForScenario = ( } ) as GraphQLMock[]; - return (httpMocks as any).concat(graphQlMocks); + const websocketMocks = Object.values(websocketMocksByUrl); + + return [...httpMocks, ...graphQlMocks, ...websocketMocks]; }; /** From 5a4d8d8744fe9952459cf56dd6a6f79365f9a9c9 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 14 Sep 2021 16:16:20 +0100 Subject: [PATCH 07/15] remove log --- src/mocks.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mocks.ts b/src/mocks.ts index f2e88bb..2b03580 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -303,7 +303,6 @@ function handleGraphQLMock({ url, operations }: GraphQLMock) { } const handleWebsocketMock = ({ url, server }: WebSocketMock) => { - console.log('makeing, ', url); server(new MockServer(url)); }; From cdcb25a2b5976aa14dfbf664532f0699b66f80f1 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 14 Sep 2021 17:04:04 +0100 Subject: [PATCH 08/15] v4.2.0 --- README.md | 3 +++ package.json | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ba10536..783c815 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # data-mocks +# modified by aiden for websocket support. +# will hopefully be deleted soon when merged in to upstream + [![GitHub license](https://img.shields.io/github/license/ovotech/data-mocks.svg)](https://github.com/grug/data-mocks) ![npm](https://img.shields.io/npm/dm/data-mocks.svg) diff --git a/package.json b/package.json index beda6b3..7a76385 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "data-mocks", - "version": "4.1.0", + "name": "@ovotech/data-mocks", + "version": "4.2.0", "main": "dist/mocks.js", "types": "dist/types.d.ts", - "repository": "git@github.com:grug/data-mocks.git", + "repository": "git@github.com:aidenscott2016/data-mocks.git", "author": "Dave Cooper ", "license": "MIT", - "homepage": "https://github.com/grug/data-mocks", + "homepage": "https://github.com/aidenscott2016/data-mocks", "scripts": { "clean": "rimraf dist", "prepublishOnly": "yarn build", From 34b035664de264478871a4d30218002eb6ee616b Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 14 Sep 2021 17:07:49 +0100 Subject: [PATCH 09/15] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a76385..99df55a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ovotech/data-mocks", - "version": "4.2.0", + "version": "4.2.1", "main": "dist/mocks.js", "types": "dist/types.d.ts", "repository": "git@github.com:aidenscott2016/data-mocks.git", From ce1f4e01fe5bd9f09fe3091f5b3d2f6d42b788a6 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 14 Sep 2021 17:22:24 +0100 Subject: [PATCH 10/15] post review change --- jest.config.js | 1 - src/mocks.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jest.config.js b/jest.config.js index 96738a6..2786a53 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,4 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', - verbose: true, }; diff --git a/src/mocks.ts b/src/mocks.ts index 2b03580..5342c1d 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -172,6 +172,7 @@ function handleRestMock({ status: responseCode, headers: responseHeaders, }; + switch (method) { case 'GET': fetchMock.get(url, () => addDelay(delay).then(() => finalResponse), { @@ -210,7 +211,8 @@ function handleRestMock({ } } -/** const graphQLMocks = mocks.filter(ethod for a GraphQL mock. +/** + * Mocks the right HTTP method for a GraphQL mock. */ function handleGraphQLMock({ url, operations }: GraphQLMock) { const graphQLErrorResponse = { errors: [] }; From ea3a10f596491da402a42c807ac9c3fe986c48d4 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 24 Sep 2021 12:48:47 +0100 Subject: [PATCH 11/15] restore origin package json --- package.json | 9 ++++----- yarn.lock | 25 ------------------------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 99df55a..cbf0f49 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "@ovotech/data-mocks", - "version": "4.2.1", + "name": "data-mocks", + "version": "4.1.0", "main": "dist/mocks.js", "types": "dist/types.d.ts", - "repository": "git@github.com:aidenscott2016/data-mocks.git", + "repository": "git@github.com:grug/data-mocks.git", "author": "Dave Cooper ", "license": "MIT", - "homepage": "https://github.com/aidenscott2016/data-mocks", + "homepage": "https://github.com/grug/data-mocks", "scripts": { "clean": "rimraf dist", "prepublishOnly": "yarn build", @@ -23,7 +23,6 @@ }, "dependencies": { "fetch-mock": "^9.4.0", - "mock-socket": "^9.0.3", "query-string": "^5.1.1", "xhr-mock": "^2.5.1" }, diff --git a/yarn.lock b/yarn.lock index 3251c1c..8c12ec6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,13 +3431,6 @@ mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mock-socket@^9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.0.3.tgz#4bc6d2aea33191e4fed5ec71f039e2bbeb95e414" - integrity sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw== - dependencies: - url-parse "^1.4.4" - mri@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.5.tgz#ce21dba2c69f74a9b7cf8a1ec62307e089e223e0" @@ -3925,11 +3918,6 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - react-is@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -4113,11 +4101,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4756,14 +4739,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-parse@^1.4.4: - version "1.5.3" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" From 49c05601e0e357d924fda7bacb4837a690e9713f Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 24 Sep 2021 13:22:51 +0100 Subject: [PATCH 12/15] bits and pieces --- package.json | 1 + src/mocks.test.ts | 2 -- src/mocks.ts | 41 ++++++++++++++++++++--------------------- tsconfig.json | 4 ++-- yarn.lock | 25 +++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index cbf0f49..beda6b3 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "fetch-mock": "^9.4.0", + "mock-socket": "^9.0.3", "query-string": "^5.1.1", "xhr-mock": "^2.5.1" }, diff --git a/src/mocks.test.ts b/src/mocks.test.ts index 42aa9d7..64d6d55 100644 --- a/src/mocks.test.ts +++ b/src/mocks.test.ts @@ -1,7 +1,6 @@ import axios from 'axios'; import fetchMock from 'fetch-mock'; import 'isomorphic-fetch'; -import { Server as MockSever } from 'mock-socket'; import XHRMock, { proxy } from 'xhr-mock'; import { extractScenarioFromLocation, @@ -27,7 +26,6 @@ describe('data-mocks', () => { method: 'WEBSOCKET', server: (s) => { s.on('connection', (socket) => { - console.log('conneted'); onConnect(); socket.on('message', (req) => { onMessage(); diff --git a/src/mocks.ts b/src/mocks.ts index 5342c1d..1d5520a 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -45,17 +45,7 @@ export const injectMocks = ( if (!mocks || mocks.length === 0) { throw new Error('Unable to instantiate mocks'); } - - const restMocks = mocks.filter( - (m) => !['GRAPHQL', 'WEBSOCKET'].includes(m.method) - ) as HttpMock[]; - const graphQLMocks = mocks.filter( - (m) => m.method === 'GRAPHQL' - ) as GraphQLMock[]; - - const webSocketMocks = mocks.filter( - (m) => m.method === 'WEBSOCKET' - ) as WebSocketMock[]; + const { restMocks, graphQLMocks, webSocketMocks } = getMocksByType(mocks); restMocks.forEach(handleRestMock); graphQLMocks.forEach(handleGraphQLMock); @@ -87,16 +77,11 @@ export const reduceAllMocksForScenario = ( const mocks = defaultMocks.concat(scenarioMocks); - const initialHttpMocks = mocks.filter( - (m) => !['GRAPHQL', 'WEBSOCKET'].includes(m.method) - ) as HttpMock[]; - const initialGraphQlMocks = mocks.filter( - ({ method }) => method === 'GRAPHQL' - ) as GraphQLMock[]; - - const initialWebsocketMocks = mocks.filter( - (m) => m.method === 'WEBSOCKET' - ) as WebSocketMock[]; + const { + restMocks: initialHttpMocks, + graphQLMocks: initialGraphQlMocks, + webSocketMocks: initialWebsocketMocks, + } = getMocksByType(mocks); const websocketMocksByUrl = initialWebsocketMocks.reduce< Record @@ -313,3 +298,17 @@ const handleWebsocketMock = ({ url, server }: WebSocketMock) => { */ const addDelay = (delay: number) => new Promise((res) => setTimeout(res, delay)); + +const getMocksByType = (mocks: Mock[]) => { + const restMocks = mocks.filter( + (m) => !['GRAPHQL', 'WEBSOCKET'].includes(m.method) + ) as HttpMock[]; + const graphQLMocks = mocks.filter( + (m) => m.method === 'GRAPHQL' + ) as GraphQLMock[]; + + const webSocketMocks = mocks.filter( + (m) => m.method === 'WEBSOCKET' + ) as WebSocketMock[]; + return { restMocks, graphQLMocks, webSocketMocks }; +}; diff --git a/tsconfig.json b/tsconfig.json index 226165b..57fe7d8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,8 +7,8 @@ "removeComments": true /* Do not emit comments to output. */, "strict": true /* Enable all strict type-checking options. */, "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, - "noUnusedLocals": false /* Report errors on unused locals. */, - "noUnusedParameters": false /* Report errors on unused parameters. */, + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, "lib": ["es2017", "dom"], "esModuleInterop": true }, diff --git a/yarn.lock b/yarn.lock index 8c12ec6..3251c1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,6 +3431,13 @@ mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mock-socket@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.0.3.tgz#4bc6d2aea33191e4fed5ec71f039e2bbeb95e414" + integrity sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw== + dependencies: + url-parse "^1.4.4" + mri@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.5.tgz#ce21dba2c69f74a9b7cf8a1ec62307e089e223e0" @@ -3918,6 +3925,11 @@ querystring@0.2.0, querystring@^0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + react-is@^16.12.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -4101,6 +4113,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4739,6 +4756,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url-parse@^1.4.4: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" From 8df38c7c9b69820581b959d21e282fc48fea5ca4 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 24 Sep 2021 13:36:08 +0100 Subject: [PATCH 13/15] readme --- README.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 783c815..1b3eca4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # data-mocks # modified by aiden for websocket support. + # will hopefully be deleted soon when merged in to upstream [![GitHub license](https://img.shields.io/github/license/ovotech/data-mocks.svg)](https://github.com/grug/data-mocks) @@ -8,7 +9,7 @@ -Library (written in TypeScript) to mock REST and GraphQL requests +Library (written in TypeScript) to mock REST, GraphQ, and Websocket requests @@ -96,13 +97,13 @@ import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; async function setupMocks() { - const { injectMocks, extractScenarioFromLocation } = await import( - 'data-mocks' - ); - // You could just define your mocks inline if you didn't want to import them. - const { getMocks } = await import('./path/to/your/mock/definitions'); + const { injectMocks, extractScenarioFromLocation } = await import( + 'data-mocks' + ); + // You could just define your mocks inline if you didn't want to import them. + const { getMocks } = await import('./path/to/your/mock/definitions'); - injectMocks(getMocks(), extractScenarioFromLocation(window.location)); + injectMocks(getMocks(), extractScenarioFromLocation(window.location)); } async function main() { @@ -306,6 +307,38 @@ const Component = () => { }; ``` +### Basic Websocket server mock injection + +To mock a WebSocket server you should provide a function which takes a single `Server` (provided by `mock-socket`) as a paremeter. On this mock server parameter you can set your callbacks as normal. Please not that due to limitations in the underlying websocket mocking library, the url _must_ be supplied as a string, not a regular expression + +```ts +const wsMock: WebSocketServerMock = s => { + return s.on('connection', socket => { + socket.on('message', _ => { + socket.send('hello world') + } + }); + }); +}; + +const mocks = { + default: [ + { + url: 'ws://localhost' //notice this is NOT a regular expression + method: 'WEBSOCKET', + server: wsMock, + } + ] +}; + +injectMocks(mocks, extractScenarioFromLocation(window.location)); + + +const socket = new WebSocket('ws://localhost') +socket.send('hello') + +``` + ## Exported types ### Scenarios @@ -334,6 +367,14 @@ const Component = () => { | method | string | ✅ | Must be 'GRAPHQL' to specify that this is a GraphQL mock. | | operations | Array\ | ✅ | Array of GraphQL operations for this request. | +### WebSocketMock + +| Property | Type | Required | Description | +| -------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------- | +| url | string | ✅ | Regular expression that matches part of the URL. | +| method | string | ✅ | Must be 'WEBSOCKET' to specify that this is a Websocket mock. | +| server | function | ✅ | a function which takes a server as a parameter. Here you will set up the functionality of the server to mock. | + ### Mock Union type of [`HttpMock`](#HttpMock) and [`GraphQLMock`](#GraphQLMock). From 0340b2d9070ae4b72f412c00b86dce9800e79136 Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 24 Sep 2021 14:14:31 +0100 Subject: [PATCH 14/15] Update README.md Co-authored-by: Dave Cooper --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b3eca4..420d8c0 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ -Library (written in TypeScript) to mock REST, GraphQ, and Websocket requests +Library (written in TypeScript) to mock REST, GraphQL, and Websocket requests From c7f7f4b5f07ef1033a2dd2c277e5748596089a3d Mon Sep 17 00:00:00 2001 From: Aiden Date: Fri, 24 Sep 2021 14:14:48 +0100 Subject: [PATCH 15/15] readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 420d8c0..93d2a4a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # data-mocks -# modified by aiden for websocket support. - -# will hopefully be deleted soon when merged in to upstream - [![GitHub license](https://img.shields.io/github/license/ovotech/data-mocks.svg)](https://github.com/grug/data-mocks) ![npm](https://img.shields.io/npm/dm/data-mocks.svg)