
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@web/mocks
Advanced tools
MSW integration layer for usage with @web/dev-server, @web/test-runner and @web/dev-server-storybook.
feature-a/demo/mocks.js:
import { http } from '@web/mocks/http.js';
import mocksFromAnotherFeature from 'another-feature/demo/mocks.js';
/**
* Define mock scenarios
*/
export default {
/**
* Return an object from the handler
*/
default: [http.get('/api/foo', context => Response.json({ foo: 'bar' }))],
/**
* Return native `Response` object from the handler
*/
error: [http.get('/api/foo', context => new Response('', { status: 400 }))],
/**
* Handle additional custom logic in the handler, based on url, searchparams, whatever
*/
custom: [
/**
* Customize based on searchParams
*/
http.get('/api/users', ({ request }) => {
const searchParams = new URL(request.url).searchParams;
if (searchParams.get('user') === '123') {
return Response.json({ id: '123', name: 'frank' });
}
return Response.json({ id: '456', name: 'bob' });
}),
/**
* Customize based on params
*/
http.get('/api/users/:id', ({ params }) => {
if (params.id === '123') {
return new Response('', { status: 400 });
}
return Response.json({ id: '456', name: 'bob' });
}),
/**
* Customize based on cookies
*/
http.get('/api/abtest', ({ cookies }) => {
return Response.json({ abtest: cookies.segment === 'business' });
}),
],
/**
* Provide an async fn, a fn returning an object, a fn returning a Response, or just an object
*/
returnValues: [
http.get('/api/foo', async context => Response.json({ foo: 'bar' })),
http.get(
'/api/foo',
async context => new Response(JSON.stringify({ foo: 'bar' }), { status: 200 }),
),
http.get('/api/foo', context => Response.json({ foo: 'bar' })),
http.get('/api/foo', context => new Response(JSON.stringify({ foo: 'bar' }), { status: 200 })),
],
importedMocks: [
mocksFromAnotherFeature.default,
http.get('/api/foo', () => Response.json({ foo: 'bar' })),
],
};
The context object that gets passed to the handler includes:
http.get('/api/foo', ({ request, cookies, params }) => {
return Response.json({ foo: 'bar' });
});
request the native Request objectcookies an object based on the request cookiesparams an object based on the request params@web/dev-server/@web/dev-server-storybook/@web/storybook-builderfeature-a/web-dev-server.config.mjs:
+// for Storybook 7+ (@web/storybook-builder)
+// no need to do anything here
+// this file is not even needed for "@web/storybook-builder"
import { storybookPlugin } from '@web/dev-server-storybook';
+// for Storybook 6 (@web/dev-server-storybook)
+import { mockPlugin } from '@web/mocks/plugins.js';
export default {
nodeResolve: true,
plugins: [
+ // for Storybook 6 (@web/dev-server-storybook)
+ mockPlugin(),
storybookPlugin({ type: 'web-components' })
],
};
You can also add the mockRollupPlugin to your .storybook/main.cjs config for when you're bundling your Storybook to deploy somewhere; your mocks will be deployed along with your Storybook, and will work in whatever environment you deploy them to.
feature-a/.storybook/main.cjs:
module.exports = {
stories: ['../stories/**/*.stories.{js,md,mdx}'],
+ // for Storybook 7+ (@web/storybook-builder)
+ // no need to do anything here
+ // for Storybook 6 (@web/dev-server-storybook)
+ rollupConfig: async config => {
+ const { mockRollupPlugin } = await import('@web/mocks/plugins.js');
+ config.plugins.push(mockRollupPlugin());
+ return config;
+ },
};
mockRollupPlugin configuration options
interceptor, which can be useful to handle things like rewriting api. prefixes made in requests, for example:
mockRollupPlugin({
interceptor: `
const domain = window.location.hostname;
const apiDomain = "api." + domain;
const { fetch: originalFetch } = window;
async function fetch(request) {
request = new Request(request);
const resolvedURL = new URL(request.url, window.location);
if (resolvedURL.hostname === apiDomain) {
// rewrite hostname without api.
resolvedURL.hostname = domain;
const init = {};
for (const property in request) {
init[property] = request[property];
}
request = new Request(resolvedURL.href, init);
if (request.body != null) {
request = new Request(request, { body: await request.arrayBuffer() });
}
}
return originalFetch(request);
}
window.fetch = fetch;
`,
});
This can be used to avoid CORS issues when deploying your Storybooks.
In the Storybook 7+ (@web/storybook-builder) you can achieve the same by using native Storybook API previewHead:
// .storybook/main.js
/** @type { import('@web/storybook-framework-web-components').StorybookConfig } */
const config = {
framework: {
name: '@web/storybook-framework-web-components',
},
// ...
previewHead(head) {
return `
${process.env.NODE_ENV === 'production' ? `<script>${interceptor}</script>` : ''}
${head}
`;
},
};
export default config;
And add the addon:
feature-a/.storybook/main.cjs:
module.exports = {
stories: ['../stories/**/*.stories.{js,md,mdx}'],
+ // for Storybook 7+ (@web/storybook-builder)
+ addons: ['@web/mocks/storybook-addon'],
+ // for Storybook 6 (@web/dev-server-storybook)
+ addons: ['@web/mocks/storybook/addon.js'],
rollupConfig: async config => {
const { mockRollupPlugin } = await import('@web/mocks/plugins.js');
config.plugins.push(mockRollupPlugin());
return config;
},
};
feature-a/.storybook/preview.js:
+// for Storybook 7+ (@web/storybook-builder)
+// no need to do anything here
+// for Storybook 6 (@web/dev-server-storybook)
+ import { withMocks } from '@web/mocks/storybook/decorator.js';
+ export const decorators = [withMocks];
feature-a/stories/default.stories.js:
import { html } from 'lit';
import { http } from '@web/mocks/http.js';
import mocks from '../demo/mocks.js';
export const Default = () => html`<feature-a></feature-a>`;
Default.story = {
parameters: {
mocks: mocks.default,
// or
mocks: [
mocks.default,
otherMocks.error,
http.get('/api/bar', () => Response.json({ bar: 'bar' })),
http.post('/api/baz', () => new Response('', { status: 400 })),
],
// or
mocks: [
http.get('/api/users/:id', ({ params }) => {
if (params.id === '123') {
return Response.json({ name: 'frank' });
}
return Response.json({ name: 'bob' });
}),
],
},
};
@web/test-runnerThe registerMockRoutes function will ensure the service worker is installed, and the mockPlugin takes care of resolving the service worker file, so users don't have to keep this one-time generated service worker file in their own project roots.
feature-a/web-test-runner.config.mjs:
import { mockPlugin } from '@web/mocks/plugins.js';
export default {
nodeResolve: true,
files: ['test/**/*.test.js'],
plugins: [mockPlugin()],
};
feature-a/test/my-test.test.js:
import { registerMockRoutes } from '@web/mocks/browser.js';
import { http } from '@web/mocks/http.js';
import mocks from '../demo/mocks.js';
import featureBmocks from 'feature-b/demo/mocks.js';
describe('feature-a', () => {
it('works', async () => {
registerMockRoutes(http.get('/api/foo', () => Response.json({ foo: 'foo' })));
const response = await fetch('/api/foo').then(r => r.json());
expect(response.foo).to.equal('foo');
});
it('works', () => {
registerMockRoutes(
// Current project's mocks
mocks.default, // is an array, arrays get flattened in the integration layer
// Third party project's mocks, that uses a different version of MSW internally
featureBmocks.default,
// Additional mocks
http.get('/api/baz', context => Response.json({ baz: 'baz' })),
);
});
});
You can also mock requests in node.js:
import { registerMockRoutes } from '@web/mocks/node.js';
import { http } from '@web/mocks/http.js';
registerMockRoutes(
http.get('/api/foo', () => new Response(JSON.stringify({ foo: 'bar' }), { status: 200 })),
);
const r = await fetch('/api/foo').then(r => r.json());
console.assert(r.foo === 'bar');
Large applications may have many features, that themself may depend on other features internally. Consider the following example:
feature-a uses feature-b internally. feature-a wants to reuse the mocks of feature-b, but the versions of msw are different.
feature-a uses msw@1.0.0feature-b uses msw@2.0.0import mocks from '../demo/mocks.js';
import featureBmocks from 'feature-b/demo/mocks.js';
const Default = () => html`<feature-a></feature-a>`; // uses `feature-b` internally
Default.story = {
parameters: {
mocks: [
mocks.default, // uses MSW@1.0.0
featureBmocks.default, // ❌ uses MSW@2.0.0, incompatible mocks -> MSW@2.0.0 may expect a different service worker, or different API!
],
},
};
msw@2.0.0 may have a different API or it's service worker may expect a different message, event, or data format. In order to ensure forward compatibility, we expose a "middleman" function:
import { http } from '@web/mocks/http.js';
http.get('/api/foo', () => Response.json({ foo: 'bar' }));
The middleware function simply returns an object that looks like:
{
method: 'get',
endpoint: '/api/foo',
handler: () => Response.json({foo: 'bar'})
}
This way we can support multiple versions of msw inside of our integration layer by acting as a bridge of sorts; the function people define mocks with doesn't directly depend on msw itself, it just creates an object with the information we need to pass on to msw.
That way, feature-a's project controls the dependency on msw (via the msw integration layer package), while still being able to use mocks from other projects that may use a different version of msw themself internally.
In the wrapper, we standardize on native Request and Response objects; the handler function receives a Request object, and returns a Response object. For utility, we also pass cookies and params, since those are often used to conditionally return mocks. This means that the wrapper function only depends on standard, browser-native JS, and itself has no other dependencies, which is a good foundation to ensure forward compatibility.
The Request and Response objects used are standard JS Request and Response objects. You can read more about them on MDN.
FAQs
MSW integration for @web tooling
The npm package @web/mocks receives a total of 39 weekly downloads. As such, @web/mocks popularity was classified as not popular.
We found that @web/mocks demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.