Code Monkey home page Code Monkey logo

msw's People

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

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.

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.

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

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.

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.

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,
    })
  })
})

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()

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.

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.

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

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.

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.

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.

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.

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"
}

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?

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.

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.

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

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

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

"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).

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.

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.

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?

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/

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()

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.

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

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(...)

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.

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

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

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

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.

"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).

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!

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.

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()`

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

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

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.

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.