{"id":15368461,"url":"https://github.com/koddsson/web-dev-server-plugin-msw","last_synced_at":"2025-04-15T12:56:15.783Z","repository":{"id":139227278,"uuid":"598135314","full_name":"koddsson/web-dev-server-plugin-msw","owner":"koddsson","description":null,"archived":false,"fork":false,"pushed_at":"2023-06-19T08:27:56.000Z","size":170,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-15T12:55:33.561Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/koddsson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-06T13:30:44.000Z","updated_at":"2023-06-18T20:39:33.000Z","dependencies_parsed_at":"2024-10-16T09:41:26.461Z","dependency_job_id":"a9f629de-d37a-4ab0-a93a-0df4b8519175","html_url":"https://github.com/koddsson/web-dev-server-plugin-msw","commit_stats":{"total_commits":71,"total_committers":2,"mean_commits":35.5,"dds":"0.15492957746478875","last_synced_commit":"8a9af1f99e4b9735c0a851720f1079bff5f77ea1"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koddsson%2Fweb-dev-server-plugin-msw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koddsson%2Fweb-dev-server-plugin-msw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koddsson%2Fweb-dev-server-plugin-msw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/koddsson%2Fweb-dev-server-plugin-msw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/koddsson","download_url":"https://codeload.github.com/koddsson/web-dev-server-plugin-msw/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249076545,"owners_count":21208811,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T13:29:29.273Z","updated_at":"2025-04-15T12:56:15.776Z","avatar_url":"https://github.com/koddsson.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# msw-integration-layer\n\n[`MSW`](https://mswjs.io/) integration layer for usage with [`@web/dev-server`](https://modern-web.dev/docs/dev-server/overview/), [`@web/test-runner`](https://modern-web.dev/docs/test-runner/overview/) and [`@web/dev-server-storybook`](https://modern-web.dev/docs/dev-server/plugins/storybook/#mainjs-and-previewjs).\n\n## Defining mocks\n\n`feature-a/demo/mocks.js`:\n```js\nimport { rest } from 'msw-integration-layer/rest.js';\nimport mocksFromAnotherFeature from 'another-feature/demo/mocks.js';\n \n/**\n * Define mock scenarios\n */\nexport default {\n  /**\n   * Return an object from the handler\n   */\n  default: [\n    rest.get('/api/foo', (context) =\u003e Response.json({foo: 'bar'}))\n  ],\n  /**\n   * Return native `Response` object from the handler\n   */\n  error: [\n    rest.get('/api/foo', (context) =\u003e new Response('', {status: 400}))\n  ],\n  /**\n   * Handle additional custom logic in the handler, based on url, searchparams, whatever\n   */\n  custom: [\n    /**\n     * Customize based on searchParams\n     */\n    rest.get('/api/users', ({request}) =\u003e {\n      const searchParams = new URL(request.url).searchParams;\n\n      if(searchParams.get('user') === '123') {\n        return Response.json({ id: '123', name: 'frank' });\n      }\n   \n      return Response.json({ id: '456', name: 'bob' });\n    }),\n\n    /**\n     * Customize based on params\n     */\n    rest.get('/api/users/:id', ({params}) =\u003e {\n      if(params.id === '123') {\n        return new Response('', {status: 400});\n      }\n \n      return Response.json({ id: '456', name: 'bob' });\n    }),\n\n    /**\n     * Customize based on cookies\n     */\n    rest.get('/api/abtest', ({cookies}) =\u003e {\n      return Response.json({ abtest: cookies.segment === 'business' });\n    })\n  ],\n  /**\n   * Provide an async fn, a fn returning an object, a fn returning a Response, or just an object\n   */\n  returnValues: [\n    rest.get('/api/foo', async (context) =\u003e Response.json({foo: 'bar'})),\n    rest.get('/api/foo', async (context) =\u003e new Response(JSON.stringify({foo: 'bar'}), {status: 200})),\n    rest.get('/api/foo', (context) =\u003e Response.json({foo: 'bar'})),\n    rest.get('/api/foo', (context) =\u003e new Response(JSON.stringify({foo: 'bar'}), {status: 200})),\n  ],\n  importedMocks: [\n    mocksFromAnotherFeature.default,\n    rest.get('/api/foo', () =\u003e Response.json({foo: 'bar'}))\n  ]\n}\n```\n\n### Context\n\nThe `context` object that gets passed to the handler includes:\n\n```js\nrest.get('/api/foo', ({request, cookies, params}) =\u003e {\n  return Response.json({foo: 'bar'});\n});\n```\n\n- `request` the native `Request` object\n- `cookies` an object based on the request cookies\n- `params` an object based on the request params\n\n## `@web/dev-server`/`@web/dev-server-storybook`\n\n`feature-a/web-dev-server.config.mjs`:\n```js\nimport { storybookPlugin } from '@web/dev-server-storybook';\nimport { mockPlugin } from 'msw-integration-layer/node.js';\n\nexport default {\n  nodeResolve: true,\n  plugins: [\n    mockPlugin(),\n    storybookPlugin({ type: 'web-components' }),\n  ],\n};\n```\n\nYou can also add the `mswRollupPlugin` 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.\n\n`feature-a/.storybook/main.cjs`:\n```js\nmodule.exports = {\n  stories: ['../stories/**/*.stories.{js,md,mdx}'],\n  rollupConfig: async config =\u003e {\n    const { mswRollupPlugin } = await import('msw-integration-layer/node.js');\n    config.plugins.push(mswRollupPlugin());\n    return config;\n  },\n};\n```\n\n`feature-a/stories/default.stories.js`:\n```js\nimport { html } from 'lit';\nimport { rest } from 'msw-integration-layer/rest.js';\nimport mocks from '../demo/mocks.js';\n\nexport const Default = () =\u003e html`\u003cfeature-a\u003e\u003c/feature-a\u003e`;\nDefault.story = {\n  parameters: {\n    mocks: mocks.default,\n    // or\n    mocks: [\n      mocks.default,\n      otherMocks.error,\n      rest.get('/api/bar', () =\u003e Response.json({bar: 'bar'})),\n      rest.post('/api/baz', () =\u003e new Response('', {status: 400})),\n    ],\n    // or\n    mocks: [\n      rest.get('/api/users/:id', ({params}) =\u003e {\n        if (params.id === '123') {\n          return Response.json({name: 'frank'});\n        }\n        return Response.json({name: 'bob'});\n      })\n    ],\n  },\n};\n```\n\n## `@web/test-runner`\n\nThe `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.\n\n`feature-a/web-test-runner.config.mjs`:\n```js\nimport { mockPlugin } from 'msw-integration-layer/node.js';\n\nexport default {\n  nodeResolve: true,\n  files: ['test/**/*.test.js'],\n  plugins: [\n    mockPlugin(),\n  ],\n};\n```\n`feature-a/test/my-test.test.js`:\n\n```js\nimport { registerMockRoutes, rest } from 'msw-integration-layer';\nimport mocks from '../demo/mocks.js';\nimport featureBmocks from 'feature-b/demo/mocks.js';\n \ndescribe('feature-a', () =\u003e {\n  it('works', async () =\u003e {\n    registerMockRoutes(rest.get('/api/foo', () =\u003e Response.json({foo: 'foo'})));\n\n    const response = await fetch('/api/foo').then(r =\u003e r.json());\n    expect(response.foo).to.equal('foo');\n  });\n\n  it('works', () =\u003e {\n    registerMockRoutes(\n      // Current project's mocks\n      mocks.default, // is an array, arrays get flattened in the integration layer\n \n      // Third party project's mocks, that uses a different version of MSW internally\n      featureBmocks.default,\n \n      // Additional mocks\n      rest.get('/api/baz', (context) =\u003e Response.json({baz: 'baz'}))\n    )\n  });\n});\n```\n\n## Rationale\n\n### Why not use MSW directly?\n\nLarge applications may have many features, that themself may depend on other features internally. Consider the following example:\n\n`feature-a` uses `feature-b` internally. `feature-a` wants to reuse the mocks of `feature-b`, but the versions of msw are different.\n\n- `feature-a` uses `msw@1.0.0`\n- `feature-b` uses `msw@2.0.0`\n\n```js\nimport mocks from '../demo/mocks.js';\nimport featureBmocks from 'feature-b/demo/mocks.js';\n \nconst Default = () =\u003e html`\u003cfeature-a\u003e\u003c/feature-a\u003e`; // uses `feature-b` internally\nDefault.story = {\n  parameters: {\n    mocks: [\n      mocks.default, // uses MSW@1.0.0\n      featureBmocks.default // ❌ uses MSW@2.0.0, incompatible mocks -\u003e MSW@2.0.0 may expect a different service worker, or different API!\n    ]\n  }\n}\n```\n\n`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:\n\n```js\nimport { rest } from 'msw-integration-layer/rest.js';\n\nrest.get('/api/foo', () =\u003e Response.json({foo: 'bar'}));\n```\n\nThe middleware function simply returns an object that looks like:\n\n```js\n{\n  method: 'get',\n  endpoint: '/api/foo',\n  handler: () =\u003e Response.json({foo: 'bar'})\n}\n```\n\nThis 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.\n\nThat 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.\n\nIn 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.\n\n### Requests/Responses\n\nThe `Request` and `Response` objects used are standard JS `Request` and `Response` objects. You can read more about them on MDN.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoddsson%2Fweb-dev-server-plugin-msw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkoddsson%2Fweb-dev-server-plugin-msw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkoddsson%2Fweb-dev-server-plugin-msw/lists"}