Skip to content

Commit

Permalink
fix(msw): remove unusable integration test APIs
Browse files Browse the repository at this point in the history
The `registerServerMocks` and `resetServerMocks` APIs in the
`hops-msw/integration` package cannot work, because the cookie on the
response will be set for the Node.js process which called them instead
of being set in the browser which needs them.
This commit removes the broken APIs and adds a heavily commented example
to the integration tests.
  • Loading branch information
ZauberNerd authored and hops-release-bot[bot] committed Jul 29, 2021
1 parent 6e43746 commit 89c1b24
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 79 deletions.
4 changes: 0 additions & 4 deletions packages/msw/integration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,4 @@ declare module 'hops-msw/integration' {
export function mockPutRequest(pathName: string, data: Data): Mock;

export function mockDeleteRequest(pathName: string, data: Data): Mock;

export function registerServerMocks(...mocks: Mock[]): Promise<void>;

export function resetServerMocks(): Promise<void>;
}
29 changes: 0 additions & 29 deletions packages/msw/integration.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
// @ts-check

const { fetch } = require('cross-fetch');

const getMockEndpoint = (pathName) => {
if (typeof process.env.MOCK_HOST !== 'string') {
throw new Error('Please define MOCK_HOST.');
}

return new URL(pathName, process.env.MOCK_HOST).toString();
};

/** @type {import('hops-msw/integration').mockGraphQLQuery} */
const mockGraphQLQuery = (operationName, data) => {
return {
Expand Down Expand Up @@ -70,22 +60,6 @@ const mockDeleteRequest = (pathName, data) => {
};
};

/** @type {import('hops-msw/integration').registerServerMocks} */
const registerServerMocks = async (...mocks) => {
await fetch(getMockEndpoint('/_msw/register'), {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({ mocks }),
});
};

/** @type {import('hops-msw/integration').resetServerMocks} */
const resetServerMocks = async () => {
await fetch(getMockEndpoint('/_msw/reset'));
};

module.exports = {
mockGraphQLQuery,
mockGraphQLMutation,
Expand All @@ -94,7 +68,4 @@ module.exports = {
mockPostRequest,
mockPutRequest,
mockDeleteRequest,

registerServerMocks,
resetServerMocks,
};
35 changes: 27 additions & 8 deletions packages/msw/mixin.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ module.exports = class MswMixin extends Mixin {
mockServer.listen();
process.on('SIGTERM', () => mockServer.close());

middlewares.parse.push(cookieParser());

middlewares.files.push({
path: this.config.mockServiceWorkerUri,
handler: (_, res) => {
Expand All @@ -77,16 +75,34 @@ module.exports = class MswMixin extends Mixin {
},
});

middlewares.initial.push({
method: 'options',
path: '/_msw/register',
handler: (req, res) => {
res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
});
res.status(200).end();
},
});

middlewares.initial.push({
method: 'post',
path: '/_msw/register',
handler: [
bodyParserJson(),
cookieParser(),
(req, res) => {
const { mocks } = req.body;

debug('/_msw/register called with:', mocks);

res.set({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
});

res.cookie('mswWaitForBrowserMocks', 'true');

mocks.forEach(({ type, method, identifier, data }) => {
Expand Down Expand Up @@ -133,15 +149,18 @@ module.exports = class MswMixin extends Mixin {
middlewares.initial.push({
method: 'get',
path: '/_msw/reset',
handler: (_, res) => {
debug('/_msw/reset called');
handler: [
cookieParser(),
(_, res) => {
debug('/_msw/reset called');

mockServer.resetHandlers();
mockServer.resetHandlers();

res.clearCookie('mswWaitForBrowserMocks');
res.clearCookie('mswWaitForBrowserMocks');

res.type('text/plain').end('ok');
},
res.type('text/plain').end('ok');
},
],
});
}
};
141 changes: 103 additions & 38 deletions packages/spec/integration/redux/__tests__/mocked.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,73 @@
const urlJoin = require('url-join');
const {
mockGetRequest,
registerServerMocks,
resetServerMocks,
} = require('hops-msw/integration');
const { mockGetRequest } = require('hops-msw/integration');
const { handleConsoleOutput } = require('../../../helpers');

// Create your own functions to register / reset browser and server
// mocks.
// Registering server mocks means:
// - any API calls on the server-side will be mocked by whatever you
// provide
// - the browser will then stop rendering until browser mocks are
// registered or `window.hopsMswMocksRegistered` is set to true
async function registerServerMocks(page, baseUrl, ...mocks) {
await page.evaluate(
(baseUrl, mocks) => {
return fetch(new URL('/_msw/register', baseUrl).toString(), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ mocks }),
});
},
baseUrl,
mocks
);
}

// Registering browser mocks means:
// - the browser will interrupt client-side rendering until mocks are
// registered or `window.hopsMswMocksRegistered` is set to true
// - these mocks will then be passed to the mock-service-worker service
// worker and be evaluated in the browser
async function registerBrowserMocks(page, ...mocks) {
await page.evaluate((mocks) => {
window.hopsMswMocks = window.hopsMswMocks || [];

if (mocks.length) {
window.hopsMswMocks.push(...mocks);
} else if (!window.hopsMswMocksReady) {
window.hopsMswMocksRegistered = true;
} else {
window.hopsMswMocksReady();
}
}, mocks);
}

// Reset the server mocks if you don't want the browser to interrupt
// the rendering process anymore
async function resetServerMocks(page, baseUrl) {
await page.evaluate(
(baseUrl) => fetch(new URL('/_msw/reset', baseUrl).toString()),
baseUrl
);
}

// Reset the browser mocks if you don't want mock-service-worker to
// intercept client-side network calls anymore
async function resetBrowserMocks(page) {
await page.evaluate(() => {
window.hopsMswMocksReset();
});
}

// A good way to remove the complexity of browser/server mocks would be
// to create a helper function such as:
// `visitPageWithMocks(page, url, ...mocks)`
// which first registers server mocks, then visits the page and then
// registers the browser mocks to continue rendering

describe('development server with msw mocks', () => {
let url;
let baseUrl;

beforeAll(async () => {
const { getUrl } = HopsCLI.start(
Expand All @@ -17,43 +77,43 @@ describe('development server with msw mocks', () => {
},
'--fast-dev'
);
url = await getUrl();
process.env.MOCK_HOST = url;
});

afterEach(async () => {
await resetServerMocks();
baseUrl = await getUrl();
});

describe('Adding up to 12', () => {
let page;
let getInnerText;
let getElementByText;

beforeEach(async () => {
await registerServerMocks(
mockGetRequest(urlJoin(url, '/api'), { value: 5 })
const pptr = await createPage();
page = pptr.page;
getInnerText = pptr.getInnerText;
getElementByText = pptr.getElementByText;

page.on('console', (msg) => handleConsoleOutput(msg));

registerServerMocks(
page,
baseUrl,
mockGetRequest(new URL('/api', baseUrl).toString(), { value: 5 })
);
});

afterEach(async () => {
if (page) {
await page.evaluate(() => {
window.hopsMswMocksReset();
});
await resetServerMocks(page, baseUrl);
await resetBrowserMocks(page);
await page.close();
}
});

it('will show the mocked counter values 5 and 12', async () => {
const { getInnerText, getElementByText, ...result } = await createPage();
page = result.page;
page.on('console', (msg) => handleConsoleOutput(msg));
await page.goto(urlJoin(url, '/increment-fetch'), {
await page.goto(new URL('/increment-fetch', baseUrl).toString(), {
waitUntil: 'networkidle2',
});
await page.evaluate((mock) => {
window.hopsMswMocks = window.hopsMswMocks || [];
window.hopsMswMocks.push(mock);
}, mockGetRequest('/api', { value: 7 }));

await registerBrowserMocks(page, mockGetRequest('/api', { value: 7 }));

await expect(getInnerText('counter')).resolves.toBe('5');

Expand All @@ -70,33 +130,38 @@ describe('development server with msw mocks', () => {

describe('Adding up to 40', () => {
let page;
let getInnerText;
let getElementByText;

beforeEach(async () => {
const pptr = await createPage();
page = pptr.page;
getInnerText = pptr.getInnerText;
getElementByText = pptr.getElementByText;

page.on('console', (msg) => handleConsoleOutput(msg));

await registerServerMocks(
mockGetRequest(urlJoin(url, '/api'), { value: 13 })
page,
baseUrl,
mockGetRequest(new URL('/api', baseUrl).toString(), { value: 13 })
);
});

afterEach(async () => {
if (page) {
await page.evaluate(() => {
window.hopsMswMocksReset();
});
await resetServerMocks(page, baseUrl);
await resetBrowserMocks(page);
await page.close();
}
});

it('will show the mocked counter values 13 and 40', async () => {
const { getInnerText, getElementByText, ...result } = await createPage();
page = result.page;
page.on('console', (msg) => handleConsoleOutput(msg));
await page.goto(urlJoin(url, '/increment-fetch'), {
await page.goto(new URL('/increment-fetch', baseUrl).toString(), {
waitUntil: 'networkidle2',
});
await page.evaluate((mock) => {
window.hopsMswMocks = window.hopsMswMocks || [];
window.hopsMswMocks.push(mock);
}, mockGetRequest('/api', { value: 27 }));

await registerBrowserMocks(page, mockGetRequest('/api', { value: 27 }));

await expect(getInnerText('counter')).resolves.toBe('13');

Expand Down

0 comments on commit 89c1b24

Please sign in to comment.