Giter Site home page Giter Site logo

mswjs / msw Goto Github PK

View Code? Open in Web Editor NEW
14.6K 61.0 455.0 10.46 MB

Seamless REST/GraphQL API mocking library for browser and Node.js.

Home Page: https://mswjs.io

License: MIT License

JavaScript 4.43% TypeScript 95.29% HTML 0.14% Shell 0.14%
msw mock mocking-framework service-worker mock-service-worker mocking mocking-library api-mocking devtools api

msw's Introduction

MSW 2.0 is finally here! 🎉 Read the Release notes and please follow the Migration guidelines to upgrade. If you're having any questions while upgrading, please reach out in our Discord server.

We've also recorded the most comprehensive introduction to MSW ever. Learn how to mock APIs like a pro in our official video course:

Mock REST and GraphQL APIs with Mock Service Worker

Mock Service Worker logo

Mock Service Worker

Mock Service Worker (MSW) is a seamless REST/GraphQL API mocking library for browser and Node.js.

Package version Downloads per month Discord server



Features

  • Seamless. A dedicated layer of requests interception at your disposal. Keep your application's code and tests unaware of whether something is mocked or not.
  • Deviation-free. Request the same production resources and test the actual behavior of your app. Augment an existing API, or design it as you go when there is none.
  • Familiar & Powerful. Use Express-like routing syntax to intercept requests. Use parameters, wildcards, and regular expressions to match requests, and respond with necessary status codes, headers, cookies, delays, or completely custom resolvers.

"I found MSW and was thrilled that not only could I still see the mocked responses in my DevTools, but that the mocks didn't have to be written in a Service Worker and could instead live alongside the rest of my app. This made it silly easy to adopt. The fact that I can use it for testing as well makes MSW a huge productivity booster."

Kent C. Dodds

Documentation

This README will give you a brief overview on the library but there's no better place to start with Mock Service Worker than its official documentation.

Examples

Browser

How does it work?

In-browser usage is what sets Mock Service Worker apart from other tools. Utilizing the Service Worker API, which can intercept requests for the purpose of caching, Mock Service Worker responds to intercepted requests with your mock definition on the network level. This way your application knows nothing about the mocking.

Take a look at this quick presentation on how Mock Service Worker functions in a browser:

What is Mock Service Worker?

How is it different?

  • This library intercepts requests on the network level, which means after they have been performed and "left" your application. As a result, the entirety of your code runs, giving you more confidence when mocking;
  • Imagine your application as a box. Every API mocking library out there opens your box and removes the part that does the request, placing a blackbox in its stead. Mock Service Worker leaves your box intact, 1-1 as it is in production. Instead, MSW lives in a separate box next to yours;
  • No more stubbing of fetch, axios, react-query, you-name-it;
  • You can reuse the same mock definition for the unit, integration, and E2E testing. Did we mention local development and debugging? Yep. All running against the same network description without the need for adapters of bloated configurations.

Usage example

// src/mocks.js
// 1. Import the library.
import { http, HttpResponse } from 'msw'
import { setupWorker } from 'msw/browser'

// 2. Describe network behavior with request handlers.
const worker = setupWorker(
  http.get('https://github.com/octocat', ({ request, params, cookies }) => {
    return HttpResponse.json(
      {
        message: 'Mocked response',
      },
      {
        status: 202,
        statusText: 'Mocked status',
      },
    )
  }),
)

// 3. Start request interception by starting the Service Worker.
await worker.start()

Performing a GET https://github.com/octocat request in your application will result into a mocked response that you can inspect in your browser's "Network" tab:

Chrome DevTools Network screenshot with the request mocked

Tip: Did you know that although Service Worker runs in a separate thread, your mock definition executes entirely on the client? This way you can use the same languages, like TypeScript, third-party libraries, and internal logic to create the mocks you need.

Node.js

How does it work?

There's no such thing as Service Workers in Node.js. Instead, MSW implements a low-level interception algorithm that can utilize the very same request handlers you have for the browser. This blends the boundary between environments, allowing you to focus on your network behaviors.

How is it different?

  • Does not stub fetch, axios, etc. As a result, your tests know nothing about mocking;
  • You can reuse the same request handlers for local development and debugging, as well as for testing. Truly a single source of truth for your network behavior across all environments and all tools.

Usage example

Take a look at the example of an integration test in Vitest that uses React Testing Library and Mock Service Worker:

// test/Dashboard.test.js

import React from 'react'
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'
import { render, screen, waitFor } from '@testing-library/react'
import Dashboard from '../src/components/Dashboard'

const server = setupServer(
  // Describe network behavior with request handlers.
  // Tip: move the handlers into their own module and
  // import it across your browser and Node.js setups!
  http.get('/posts', ({ request, params, cookies }) => {
    return HttpResponse.json([
      {
        id: 'f8dd058f-9006-4174-8d49-e3086bc39c21',
        title: `Avoid Nesting When You're Testing`,
      },
      {
        id: '8ac96078-6434-4959-80ed-cc834e7fef61',
        title: `How I Built A Modern Website In 2021`,
      },
    ])
  }),
)

// Enable request interception.
beforeAll(() => server.listen())

// Reset handlers so that each test could alter them
// without affecting other, unrelated tests.
afterEach(() => server.resetHandlers())

// Don't forget to clean up afterwards.
afterAll(() => server.close())

it('displays the list of recent posts', async () => {
  render(<Dashboard />)

  // 🕗 Wait for the posts request to be finished.
  await waitFor(() => {
    expect(
      screen.getByLabelText('Fetching latest posts...'),
    ).not.toBeInTheDocument()
  })

  // ✅ Assert that the correct posts have loaded.
  expect(
    screen.getByRole('link', { name: /Avoid Nesting When You're Testing/ }),
  ).toBeVisible()

  expect(
    screen.getByRole('link', { name: /How I Built A Modern Website In 2021/ }),
  ).toBeVisible()
})

Don't get overwhelmed! We've prepared a step-by-step Getting started tutorial that you can follow to learn how to integrate Mock Service Worker into your project.

Despite the API being called setupServer, there are no actual servers involved! The name was chosen for familiarity, and the API was designed to resemble operating with an actual server.

Sponsors

Mock Service Worker is trusted by hundreds of thousands of engineers around the globe. It's used by companies like Google, Microsoft, Spotify, Amazon, and countless others. Despite that, this library remains a hobby project maintained in spare time and has no opportunity to financially support even a single full-time contributor.

You can change that! Consider sponsoring the effort behind one of the most innovative approaches around API mocking. Raise a topic of open source sponsorships with your boss and colleagues. Let's build sustainable open source together!

Golden Sponsors

Become our golden sponsor and get featured right here, enjoying other perks like issue prioritization and a personal consulting session with us.

Learn more on our GitHub Sponsors profile.


GitHub Codacy Workleap Chromatic

Silver Sponsors

Become our silver sponsor and get your profile image and link featured right here.

Learn more on our GitHub Sponsors profile.


Replay

Bronze Sponsors

Become our bronze sponsor and get your profile image and link featured in this section.

Learn more on our GitHub Sponsors profile.


Materialize Trigger.dev Vital

Awards & Mentions

We've been extremely humbled to receive awards and mentions from the community for all the innovation and reach Mock Service Worker brings to the JavaScript ecosystem.

Technology Radar

Solution Worth Pursuing

Technology Radar (2020–2021)

Open Source Awards 2020

The Most Exciting Use of Technology

Open Source Awards (2020)

msw's People

Contributors

aprillion avatar arnaudnyc avatar balavishnuvj avatar cdaringe avatar chentsulin avatar chrisguttandin avatar cowboyd avatar dependabot[bot] avatar garhbod avatar hauptrolle avatar hehehai avatar ivanhofer avatar jameslahm avatar kettanaito avatar knisterpeter avatar marcosvega91 avatar mattcosta7 avatar matthewleon avatar msutkowski avatar patrickmcdougle-okta avatar pmelab avatar sairus2k avatar seriouslysean avatar skipdavis1117 avatar takefumi-yoshii avatar tdeekens avatar thepassle avatar timdeschryver avatar tkamenoko avatar wwdrew avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

msw's Issues

Compare to Service Mocker

There is a Service Mocker library that also utilizes Service Workers for the purpose of API mocking. I'd like to introduce a comparison section to the MSW documentation that highlights the difference between two libraries, helping developers to choose what suits them best.

Please, if you are an active user of Service Mocker feel free to provide suggestions or edits to the comparison table below.

Comparison

Setup

Service Mocker MSW
Has client and server parts. Has only client part.
Seems like requires to set Service-Worker-Allowed header to register a worker on a path different than root. Requires to place the worker file in the public dir of your app.

I'd say that you need less setup code to start with MSW, making the adoption process faster. The substantial flaw is that MSW requires to place the mockServiceWorker.js in your public directory.

Workflow

Service Mocker MSW
Adopts Express API in its entirety. Seamless usage for Express users. Adopts Express API partially. Favors functional composition. Has custom ctx utilities for more efficient mocking.

Internals

MSW

Client -- request --> ServiceWorker -- serialize --> Client (MSW) -- get response from schema --> ServiceWorker --> `respondWith()`

Provide request url matching

What:

I propose to provide a smart request url matching.

https://github.com/kettanaito/msw/blob/a1196004c390d115d29941de405d9db571357107/serviceWorker.js#L38

Why:

To support placeholders, params, and other parts of url:

How

https://domain.com/shop/:shopId

To match all the following:

https://domain.com/shop/foo // { shopId: "foo" }
https://domain.com/shop/bar // { shopId: "bar" }

We can also parse the params and expose them within the req reference for handler(req) to access in the mocking handler.

The challenge here is that the logic happens inside a Service Worker, thus it should be done without requiring any package.

Memory leak in integration tests

Current setup of integration tests does not handle certain asynchronous operations in its setup well, which results into memory leaks often happening when testing locally.

Details

Jest warning

Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

This was caused by the missing afterAll(() => api.cleanup()) hook.

Memory leak exception

<--- Last few GCs --->

[7874:0x102812000]    85649 ms: Mark-sweep 1381.6 (1455.2) -> 1371.0 (1455.2) MB, 742.3 / 0.0 ms  (average mu = 0.160, current mu = 0.072) allocation failure scavenge might not succeed
[7874:0x102812000]    86412 ms: Mark-sweep 1378.7 (1455.7) -> 1375.3 (1436.7) MB, 635.4 / 0.0 ms  (+ 78.4 ms in 60 steps since start of marking, biggest step 5.1 ms, walltime since start of marking 739 ms) (average mu = 0.111, current mu = 0.064) allocati

<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 0xaae3f75fd61]
Security context: 0x0317e0f1e6e9 <JSObject>
    1: stringSlice(aka stringSlice) [0x317ad8136c1] [buffer.js:~589] [pc=0xaae4065e42f](this=0x0317141826f1 <undefined>,buf=0x0317639bbb89 <Uint8Array map = 0x317d50d5df1>,encoding=0x0317e0f3e981 <String[4]: utf8>,start=0,end=840098)
    2: toString [0x3170a7a0819] [buffer.js:~643] [pc=0xaae3f68c2fb](this=0x0317639bbb89 <Uint8Array map = 0x317d50d5df1>,encodi...

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x10003d035 node::Abort() [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 2: 0x10003d23f node::OnFatalError(char const*, char const*) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 3: 0x1001b8e15 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 4: 0x100586d72 v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 5: 0x100589845 v8::internal::Heap::CheckIneffectiveMarkCompact(unsigned long, double) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 6: 0x1005856ef v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 7: 0x1005838c4 v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 8: 0x100590188 v8::internal::Heap::AllocateRawWithLigthRetry(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
 9: 0x1005901df v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationSpace, v8::internal::AllocationAlignment) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
10: 0x100562064 v8::internal::Factory::NewRawTwoByteString(int, v8::internal::PretenureFlag) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
11: 0x100561ca9 v8::internal::Factory::NewStringFromUtf8(v8::internal::Vector<char const>, v8::internal::PretenureFlag) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
12: 0x1001db1b8 v8::String::NewFromUtf8(v8::Isolate*, char const*, v8::NewStringType, int) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
13: 0x1000e8822 node::StringBytes::Encode(v8::Isolate*, char const*, unsigned long, node::encoding, v8::Local<v8::Value>*) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
14: 0x100056889 void node::Buffer::(anonymous namespace)::StringSlice<(node::encoding)1>(v8::FunctionCallbackInfo<v8::Value> const&) [/Users/kettanaito/.nvm/versions/node/v10.16.3/bin/node]
15: 0xaae3f75fd61

Expected behavior

  • Integration test cleans up all the side-effects its establishes before the next integration test starts.
  • runBrowserWith and spawnServer utilities have tests that assert they clean up after themselves (i.e. asserting no process is running at the port previously occupied by the server after closing)

Area of effect

  • runBrowserWith
  • spawnServer

Adopt "matchPath" from "react-router"?

I suggest to consider adopting matchPath utility from react-router.

Why:

It's a tested function from one of the most used routing solution. It will most likely handle all the necessary routing scenarios, which are crucial for API mocking as well.

Cons:

  • Much heavier than a single matching function
  • Creates an extra dependency

Success message of "msw init" contains a misleading API example

Current behavior

Current success message of running msw init command is:

https://github.com/open-draft/msw/blob/1fc124726f2d6b1e76e5cb7bfe306d94b3061acd/cli/init.js#L29-L34

The API example illustrated in the message is misleading, as the library does not export the msw namespace, neither that namespace has the start() method.

Expected behavior

The message of successfully running the msw init command should link the developer to the documentation.

How to deal with requests made before msw has been activated?

If I have a situation like this:

start('/msw.js')

fetch('/something-that-should-be-mocked')

MSW will not be registered as ready until after the request has already been forwarded along. What do you recommend for situations like this where the app is trying to make requests immediately?

Error: Cannot find module 'chalk'

What

When we hit msw init public inside project, throws the following error:

Error: Cannot find module 'chalk'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:582:15)
    at Function.Module._load (internal/modules/cjs/loader.js:508:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (/Users/raushan/.nvm/versions/node/v10.15.3/lib/node_modules/msw/cli/init.js:3:15)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)

Why

chalk is not available inside global node_modules directory.

Screenshot 2019-09-13 at 12 51 21 AM

Integrity plugin does not update runtime global variable

Successor to #81

Current setup of the IntegrityWebpackPlugin does not update the global variable injected to contain the latest Service Worker's integrity checksum.

Reasons

First, the webpack.DefinePlugin() cannot be used with a simple string value for the injected variable, as it caches the modules by default, so the modules that reference the global variable are cached and won't access the updated checksum.

I've migrated to using webpack.DefinePlugin.runtimeValue() instead, which is supposed to skip modules caching and result into updated runtime global variable.

Using runtimeValue() didn't solve the issue, as the changes in src/mockServiceWorker.js do not trigger the bundle recompilation. When forcing the recompilation the issue is still not fixed.

Expected behavior

The global Service Worker integrity checksum variable must have the latest value in the code in the watch mode of compilation (i.e. using yarn start). This way the local development always has the Service Worker with the latest integrity.

Unable to mock URLs with localhost and port

For example:

import { msw } from "msw";

msw.post(
  "http://localhost:3000/cats",
  (req, res, { status, set, delay, json }) => {

    return res(status(404), delay(10000), json({ errorMessage: `no cats :(` }));
  }
);

msw.start();

It seems that the normalizeMask() function is turning the following URL http://localhost:3000/cats into ^http://localhost(\w+)/cats\/?$ which will not match successfully since the colon isn't alpha-numeric:

https://github.com/kettanaito/msw/blob/9932b235b650decde4df5edda6b7c26729902853/src/utils/assertUrl.ts#L24-L40

Support mocking GraphQL API

I suggest that MSW would provide a standard set of request handlers for mocking a GraphQL API.

Motivation

  • GraphQL is an adopted technology, mocking which would be beneficial for users
  • Mock-driven GraphQL development may be a good try-out for the technology, as you don't have to write any server-side code before deciding to fully adopt GraphQL

Features

  • Ability to target GraphQL operations based on type (query/mutation) and name
  • Ability to mock response data and errors according to GraphQL specification

Usage

Mocking a query

import { composeMocks, graphql } from 'msw'

const { start } = composeMocks(
  graphql.query({ operation: 'GetProjectDetail' }, (req, res, ctx) => {
    const { projectPath } = req.variables

    return res(
      ctx.data({
        data: {
          name: 'mocked-project'
        },
      }),
    )
  }),
)

start()

Mocking a mutation

import { composeMocks, graphql } from 'msw'

const { start } = composeMocks(
  graphql.mutation({ operation: 'GetProjectDetail' }, (req, res, ctx) => {
    return res(
      ctx.data({
        data: {
          name: 'mocked-project'
        },
      }),
    )
  }),
)

start()

Handle thrown errors

I'd love it if errors my resolvers throw would get converted to rejected responses with a status code of 500. That way the app doesn't come crashing down if I hit something unexpected.

Decide on class signature

Usage of classes implies multiple instances of that class. However, this is not the use case for mocking, as you would expect to have a mocking interface configured at one place, once.

const msw = new MSW()

The end usage is also ugly.

Suggestions

Internal usage of class is fine, but it may make sense to expose the instantiated instance of that class to the end developer.

import msw from 'msw'

msw.get(...)

override the match function in createHandler class.

What:

I propose providing a way to supply an alternative function to match the endpoints in the SW. which opens the app for customization according to the open/closed principle.

Why:

I am trying to achieve a very similar behavior as in this snippet: https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/mock-responses/service-worker.js#L24
I need to only serve the mock for some of the endpoints having the X-Mock-Response and regardless of the API base domain URL. which allows me to use both the live API and mock API at the same time.

How:

add additional optional param to the createHandler called matchResolver ( you might want to send more than just the URL param to be but rather the complete request metadata. or adding this same param to the start function and using it inside the interceptRequest function.

"start"/"stop" methods should return a Promise

Current behavior

Methods return nothing, impossible to listen to the registration or removal of registration of the Service Worker.

Such behavior also makes these methods un-testable, as they provide no native method to react to them.

Expected behavior

I should be able to do start().then and stop().then to react to the respective Service Worker events (registration/unregistration).

Missing yargs dependency

I tried running yarn msw create public/, which resulted in an error:

TypeError: yargs.positional is not a function
    at Object.yargs.usage.command [as builder] (/path/to/node_modules/msw/cli/msw.js:10:13)
    at Object.self.runCommand (/path/to/node_modules/yargs/lib/command.js:193:35)
    at Object.Yargs.self._parseArgs (/path/to/node_modules/yargs/yargs.js:990:30)
    at Object.get [as argv] (/path/to/node_modules/yargs/yargs.js:930:19)
    at Object.<anonymous> (/path/to/node_modules/msw/cli/msw.js:18:10)
    at Module._compile (module.js:653:30)
    at Object.Module._extensions..js (module.js:664:10)
    at Module.load (module.js:566:32)
    at tryModuleLoad (module.js:506:12)
    at Function.Module._load (module.js:498:3)
error Command failed with exit code 1.

It looks like yargs is missing as a dep from package.json in this commit: a8a9854

Trailing slash is required for request URI match

Current behavior

The following request handler:

rest.get('https://test.msw.io', ...)

Will not match https://test.msw.io/. The trailing slash if often appended automatically. For example, when performing:

fetch('https://test.msw.io')

The request URI would be resolved to https://test.msw.io/ (containing the trailing slash).

Expected behavior

  • Trailing slash is disregarded during request URI matching:
    • https://test.io === https://test.io/
  • Trailing slash is also disregarded when matching nested paths:
    • https://test.io/foo === https://test.io/foo/

Add integration tests

What:

Need to add integration tests.

Why:

Self-explanatory.

Roadmap:

  • Define minimum integration scenario setup (a story, a SPA, other)
  • Prepare the list of integration scenarios
  • Choose the testing framework (cypress, other)
  • Write integration tests

Question: How to reuse the same mocked APIs to run Jest tests?

It would be very helpful to add some documentation and one example to show how to possibly reuse the same mocked APIs code for running Jest tests.

How to set up the Jest test environment so you can leverage the same mocked APIs?

The expectation is that in the node environment, mocks will respond with a shorter latency, compared to when called from the browser.

Browser extension

I propose to develop a browser extension for MSW.

What the extension would do

  1. List all mocking routes defined via MSW.
  2. Allow to create, edit and remove mocking routes on runtime. Changes affect the page fetch resources, as they affect MSW.
  3. Basically, it's CRUD of mocking routes under a browser extension.
  4. Feature: Declared mocking routes can be exported to API Blueprint (or other formats) to be a working version of an API.

Support mocking of cookies

Mocking response cookies is forbidden by the specification, however, since MSW operates in the client scope, it may fake cookies by setting them directly on document.cookies.

API

rest.get('/user', (req, res, ctx) => {
  return res(
    ctx.cookie('name', 'value')
  )
})

Implementation draft

// context/cookie.ts
export const cookie = (name: string, value:string) => {
  return (res) => {
    document.cookies = /* write cookies manually */
    return res
  }
}

Treat integrity check failure as a warning

Originating from #81, I think that treating integrity check failure as an error and disabling the mocking is a safe, but too extreme measure for the users.

Expected behavior

  • Integrity check failure is still treated as an error, for the purpose of giving it a higher priority when the user browses's his console output.
  • Integrity check failure does not disable the mocking, for the purpose of changes that do not necessarily break backwards-compatibility. It's up to user to update the Service Worker, although it's highly recommended.

Integration tests failure: Too long with no output

Current behavior

When run, integration tests sometimes result into a timeout on CircleCI.

Expected behavior

Integration tests are run without timeouts and exceptions, and report the status of tests appropriately.

Details

I've also encountered a different exception during the CI run of integration tests, when the tests would run, but random test suites would throw an exception.

console.info node_modules/loglevelnext/factory/PrefixFactory.js:43
      ℹ 「atl」: Using [email protected] from typescript
    console.info node_modules/loglevelnext/factory/PrefixFactory.js:43
      ℹ 「atl」: Using tsconfig.json from /root/root/test/tsconfig.json
    console.info node_modules/loglevelnext/factory/PrefixFactory.js:43
      ℹ 「atl」: Checking started in a separate process...
    console.info node_modules/loglevelnext/factory/PrefixFactory.js:43
      ℹ 「atl」: Time: 7042ms
    console.error node_modules/jest-jasmine2/build/jasmine/Env.js:248
      Unhandled error
    console.error node_modules/jest-jasmine2/build/jasmine/Env.js:249
      Error [ERR_UNHANDLED_ERROR]: Unhandled error. (Error: Page crashed!
          at Page._onTargetCrashed (/root/root/node_modules/puppeteer/lib/Page.js:213:24)
          at CDPSession.<anonymous> (/root/root/node_modules/puppeteer/lib/Page.js:122:56)
          at CDPSession.emit (events.js:196:13)
          at CDPSession._onMessage (/root/root/node_modules/puppeteer/lib/Connection.js:200:12)
          at Connection._onMessage (/root/root/node_modules/puppeteer/lib/Connection.js:112:17)
          at WebSocket.<anonymous> (/root/root/node_modules/puppeteer/lib/WebSocketTransport.js:44:24)
          at WebSocket.onMessage (/root/root/node_modules/ws/lib/event-target.js:120:16)
          at WebSocket.emit (events.js:196:13)
          at Receiver.receiverOnMessage (/root/root/node_modules/ws/lib/websocket.js:789:20)
          at Receiver.emit (events.js:196:13)
          at Receiver.dataMessage (/root/root/node_modules/ws/lib/receiver.js:422:14)
          at Receiver.getData (/root/root/node_modules/ws/lib/receiver.js:352:17)
          at Receiver.startLoop (/root/root/node_modules/ws/lib/receiver.js:138:22)
          at Receiver._write (/root/root/node_modules/ws/lib/receiver.js:74:10)
          at doWrite (_stream_writable.js:417:12)
          at writeOrBuffer (_stream_writable.js:401:5)
          at Receiver.Writable.write (_stream_writable.js:301:11)
          at Socket.socketOnData (/root/root/node_modules/ws/lib/websocket.js:864:35)
          at Socket.emit (events.js:196:13)
          at addChunk (_stream_readable.js:290:12)
          at readableAddChunk (_stream_readable.js:271:11)
          at Socket.Readable.push (_stream_readable.js:226:10)
          at TCP.onStreamRead (internal/stream_base_commons.js:166:17))
          at Page.emit (events.js:185:17)
          at Page._onTargetCrashed (/root/root/node_modules/puppeteer/lib/Page.js:213:10)
          at CDPSession.<anonymous> (/root/root/node_modules/puppeteer/lib/Page.js:122:56)
          at CDPSession.emit (events.js:196:13)
          at CDPSession._onMessage (/root/root/node_modules/puppeteer/lib/Connection.js:200:12)
          at Connection._onMessage (/root/root/node_modules/puppeteer/lib/Connection.js:112:17)
          at WebSocket.<anonymous> (/root/root/node_modules/puppeteer/lib/WebSocketTransport.js:44:24)
          at WebSocket.onMessage (/root/root/node_modules/ws/lib/event-target.js:120:16)
          at WebSocket.emit (events.js:196:13)
          at Receiver.receiverOnMessage (/root/root/node_modules/ws/lib/websocket.js:789:20)

Such test where the failure originates would be marked as passing. It's necessary to ensure that exception does not happen, so there are no false positive tests.

Support handling XHRs?

I don't know whether this is possible, but due to the cancelation issues with promises/fetch, XHRs are actually preferable. Can msw support requests made via XHR?

Proposal: audit

What:

I suggest to implement a support for a feature called audit.

Why:

Audit allows to capture outgoing page requests and store them in the cache. Then, the route-less mocking can be performed by returning the original responses stored in the cache. This is, basically, how ServiceWorker is meant to be used for caching purposes.

How:

  • Audit mode is disabled by default
  • Audit mode must be explicitly enabled to initiate a caching behavior

Support audit mode

First, audit mode must be enabled.

msw.startAudit()

While audit mode is enabled, any outgoing requests will be stored in the ServiceWorker's cache.

Complementary, audit mode can be disabled, to prevent caching behavior.

msw.stopAudit()

On a technical level, dispatching of aforementioned methods sends a message to the ServiceWorker, and sets its internal flag to the corresponding boolean value. While the flag is enabled, ServiceWorker will try to cache the received responses in the fetch event.

(?) Should the request matching the routes be mocked? Or should audit mode explicitly disable mocking behavior while it's on?

Cache usage

Developer needs to explicitly enable to mock requests from the cache:

msw.useCache()

It should be possible to opt-out from this behavior at any point of time.

Support cache clearing

Since audited requests are stored in the cache, it should be possible to clear the cache.

msw.clearCache()

Define response mocking API

What:

I suggest to define a response mocking API supported by the library.

Why:

It is essential to have a usable API to define response mocks.

How:

Mimic res from express

Example:

msw.get('...', (req, res) => {
  res.status(301).json({ message: 'Item has been moved' })
})

Pros:

  • Familiar syntax of one of the most used nodejs server frameworks
  • Entire response mocking API is gathered under the exposed res Object

Cons:

  • Relies on res Object mutation
  • Effectively, is a mocking (the res from express is a custom Object)

Functional composition

Example:

import { response, status, json } from '–'

msw.get('...', (req) => {
  return response(
    status(301),
    json({ message: 'Item has been moved' }),
  )
})

Pros:

  • Uses composition instead of mutations, so the mock instance is passed from one transformer function to another
  • More versatile, as any function may now become a part of the mocking chain, as long as it follows the call and return signatures

Cons:

  • May not be straightforward for developers not familiar with functional programming
  • Essentials like json or status would need to be explicitly imported (may be solved by exposing them as a handler function parameters)
  • Doesn't have a fixed scope that controls the mocked response (any function can), thus is vague and may result into issues due to that

Forward any body of the request to the mock route handler

I ran into a usecase whereby I needed to personalize mock responses based on the request's body for POST requests:

msw.post(
  "http://localhost:3000/cats",
  (req, res, { status, set, delay, json }) => {

    if(req.body) {
        // do something with body
    }

    return res(status(404), delay(10000), json({ errorMessage: `no cats :(` }));
  }
);

I can send a quick PR for this if there's interest for this.

How to handle query params

I'm pretty sure this is a bug in pathToRegExp (why is this project not using path-to-regexp like many other path parsers?). But if I provide the following URL: http://localhost:8989/api/book?query=:query this gets parsed to the following regex: http:\/\/localhost:8989\/api\/book?query=(?<query>.+?(?=\/|$))\/?

You may notice that the ? for the search string is not escaped. I believe that to be a bug in the parser. If I escape it myself, then things work just fine.

"req.params" does not contain my URL path parameter

Environment

  • msw: 0.4.2

Current behavior

When defining the following request handler:

rest.get('/users/:userId', (req, res, ctx) => {
  return res(ctx.text(req.params.userId)
})

The req.params.userId reference returns undefined. I can see that all the parameters are nested in the req.params.params key, which is not the correct behavior.

Expected behavior

Request URL path parameters are listed on the req.params object, containing only key-value pairs of parameter name and parameter value. There must be no other information (like matches).

Log matched requests in a browser's console

Those requests which have matched the request handlers defined in the composeMocks() call should be logged into a browser's console.

Motivation

  • Easier and faster for the developer to see if something has been mocked

Specification

  1. Each request matched against the defined mock definition is logged into the console.
  2. The log entry of the matched request looks as follows:
[MSW] 10:10:02 GET /users
  1. The log entry is represented by console.group, clicking on which reveals the following information:

    3.1. Intercepted request.
    3.2. Request handler used.
    3.3. Resolved mocked response object.

Documentation

What:

Need to migrate the existing documentation to a new service provider.

Why:

To have stable and free documentation that won't shut down out of the blue.

Favor

Please include any topics related to MSW in the comments, I will think of where to put them in the documentation.

Support local server for complex testing scenarios

This issue originates from #77

What:

I suggest to add a support for spawning a local server for the purpose of testing.

Why:

There are complex test scenarios (such as "response patching") that assert against an actual server implementation. Relying on third-party servers brings a factor of instability to the CI pipeline of MSW. Having a local server for the sake of testing sounds like a viable option.

The local server can also replace any external dependencies and be used as an actual running server. This is something to consider.

How:

  • Write a utility function that spawns an Express server, accepts the routes and returns a Promise that resolves whenever the server is up and running (spawn at port 0 for the server to always have a dedicated port)
  • Return utility functions to perform any request against the current instance of the server (not to bind test to any specific ports)
  • Adjust the response-matching.test.ts and any other tests that require an actual server to use the introduced local server implementation
  • Close the server afterAll() test suites are finished

Shoot a live demo

It would be great to shoot a small live demo of how the process of using msw looks like. It can be converted into a GIF later and put at the top of the README file.

The demo would consist of the following steps:

  1. Installing the package.
  2. Writing mocks.js
  3. Running your app and demonstrating that the Mock Service Worker works.

Rename "composeMocks" to better describe what it does

Is your feature request related to a problem? Please describe.

I find it confusing that composeMocks() function composes request handlers, and not mocks.

Describe the solution you'd like
I think renaming composeMocks() function to composeHandlers() would provide a more domain-driven name, and won't confuse the users.

- const { start } = composeMocks(...)
+ const { start } = composeHandlers(...)

Benefits of the change

  • Aligned with the internal vocabulary
  • Function does exactly what it's called after

Drawbacks of the change

  • Longer to write
  • Not straight intuitive if you don't know the library's glossary

Alternatives to consider
I was also thinking about simply naming it compose(), but I'm afraid this will clash with the similar functional composition utility that is often called compose(). Also, composeMocks() is not the traditional compose function per-say, as it's logic is specific to MSW.

Handle multiple headers with the same name

https://github.com/kettanaito/msw/blob/fb1e53cf9cb719ea03c7a3f964461cfb84aa54e2/mockServiceWorker.js#L59-L65

Need to ensure multiple headers with the same name can be handled by MockServiceWorker.

This may also mean to adjust the current algorithm for handling headers between client/sw.

Surface

  • Adjust serialization/deserialization of headers in MSW and interceptRequest
  • Adjust public API of context.set() to support setting of multiple headers with the same name
  • Add tests

Forward leading slash to match the page's hostname

Current behavior expects the developer to either specify an entire hostname (http://backend.com/users) or use a wildcard (*/users) to handle request URI.

Motivation

It's familiar to developers to specify routes with a leading slash and expecting the hostname to be matched implicitly.

Expected behavior

When provided the route /users MSW would match any /users requests that are fired from the current page's hostname:

  • http://localhost:8080/users

However, it won't match the same paths from the different hostname:

  • https://api.dev.backend/users

Unmatched requests get logged to the console.

The lib/index.js file is built via webpack and has a console.warn in it in the match function from node-match-path:

/**
 * Matches a given url against a path.
 */
const match = (path, url) => {
    const expression = path instanceof RegExp ? path : pathToRegExp(path);
    const match = expression.exec(url) || false;
    // Matches in strict mode: match string should equal to input (url)
    // Otherwise loose matches will be considered truthy:
    // match('/messages/:id', '/messages/123/users') // true
    const matches = !!match && match[0] === match.input;
    console.warn('nmp', { path, url, match, matches  })
    return {
        matches,
        params: match && matches ? match.groups || null : null,
    };
};

But no version of node-match-path has a console.warn in it. Could we get a rebuild of this and a publish to get rid of that warning? It's kind of distracting 😅

Thanks!

Prove client-side usage API

What:

There should be a way to register the library's ServiceWorker with the proper scope.

Why:

In order for it to work, obviously.

How:

  1. ServiceWorker must be registered in the same scope as the target application.
  2. Ideally, ServiceWorker should be registered via .start() method, instead of manual registration. So that the responsibility of the registration lies on the library, not the end developer.

The end developer may have to supply some absolute path to his project's root in order to configure a proper scope.

Custom response transformer API

What:

I suggest to add a full support for custom response transformers.

Why:

With the functional composition it is easy to create custom response transformers.

  • The library must expose its default response transformers (text, json, delay, etc.) so the end developer may combine them to their liking.
  • The libarary must explain the call signature of response transformers, so custom functions can run as transformers.

How:

  • Expose default response transformers (context) from the library's module directly, so they can be reused to create custom transformers
  • Think how to make the writing of functional composition easier, especially on projects that do not use functional utils.
  • Think about the call signature of default transformers. It would be nice to have them curried, so they can be used outside of functional composition

Choose a different name

"msw" abbreviation seems to be used for magicseaweed. It would be great for a name not to overlap, or be confused with other technologies/brands.

Response Patching (following the documentation) does not work

I am using the create-react-app in this project.
Following the documentation (https://redd.gitbook.io/msw/recipes/response-patching) and through various combinations of attempts to get it working, I either end up with errors like:
Uncaught TypeError: Cannot read property 'entries' of undefined
at ServiceWorkerContainer. (index.js:1828)
or if I reconstruct the fetch call properly, it creates an infinite loop and still reports errors.

"devDependencies": {
"msw": "^0.8.2"
}

Assert "mockServiceWorker" integrity

Motivation

The library must assert the integrity of the mockServiceWorker.js file for multiple reasons:

  • Ensure the file is up-to-date, as it lives in the user's public directory and is not updated during new installations of msw package
  • Ensure the file is actually a valid MSW file, to prevent malware masking as MSW

Implementation

Publication

  1. Minimize
  2. Remove comments
  3. Generate a checksum out of the mockServiceWorker.js file content

Client-side

  1. Signal to Service Worker upon start() invocation to get the integrity information.
  2. Assert the integrity on the client-side.
  3. Display an error in case integrity fails. Suggest to run npx init <PUBLIC_DIR> to resolve the issue.

Unregister self when not activated?

I have MSW working for a project on localhost:3000, then I close that project and open a new one which does not have MSW and that starts up on localhost:3000 (the default for create-react-app based apps) and MSW hangs around, logs in the console, and generally confuses people.

Can we have MSW's service worker unregister itself when it's not activated when the app starts up? I'd be fine if this is opt-in.

Response patching for POST does not work

Using ctx.fetch inside the response resolver for a POST results in a GET being sent (with no body) instead of a POST (with a body).

Notes:

  • The req before sending the original request showed the correct body and bodyUsed:true, but looking at the req in mockServiceWorker showed the body gone.
    I suspect the problem is in context/fetch.ts, but initial scan did not show an obvious issue to me (JavaScript beginner)

I wrote an integration test to PR into the repo that replicated the issue, but I did not have permission to push up a branch.

Tests:

// response-mocking.mocks.ts:
 rest.post(
  'https://jsonplaceholder.typicode.com/posts',
  async (req, res, ctx) => {
    const originalResponse = await ctx.fetch(req)
    return res(
      ctx.json({
        ...originalResponse,
        mocked: true,
      }),
    )
  },
),
// response-patching.test.ts:
describe('given a post request to be patched', () => {
  it('should be able to properly request and patch a post', async () => {
    const REQUEST_URL = 'https://jsonplaceholder.typicode.com/posts'

    api.page.evaluate(
      (url) =>
        fetch(url, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            title: 'foo',
            body: 'bar',
            userId: 1,
          }),
        }),
      REQUEST_URL,
    )
    const res = await api.page.waitForResponse((res) => {
      return (
        // Await for the response from MSW, so that original response
        // from the same URL would not interfere.
        res.url() === REQUEST_URL && res.headers()['x-powered-by'] === 'msw'
      )
    })
    const body = await res.json()

    expect(res.status()).toBe(200)
    expect(body).toEqual({
      id: 101,
      title: 'foo',
      body: 'bar',
      userId: 1,
      mocked: true,
    })
  })
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.