Giter Site home page Giter Site logo

mswjs / interceptors Goto Github PK

View Code? Open in Web Editor NEW
527.0 10.0 119.0 2.11 MB

Low-level network interception library.

Home Page: https://npm.im/@mswjs/interceptors

License: MIT License

JavaScript 1.64% TypeScript 98.36%
node http https xhr request mock intercept interceptor nodejs request-interception

interceptors's Introduction

Latest version

@mswjs/interceptors

Low-level network interception library.

This library supports intercepting the following protocols:

  • HTTP (via the http module, XMLHttpRequest, or globalThis.fetch);
  • WebSocket (the WebSocket class in Undici and in the browser).

Motivation

While there are a lot of network mocking libraries, they tend to use request interception as an implementation detail, giving you a high-level API that includes request matching, timeouts, recording, and so forth.

This library is a barebones implementation that provides as little abstraction as possible to execute arbitrary logic upon any request. It's primarily designed as an underlying component for high-level API mocking solutions such as Mock Service Worker.

How is this library different?

A traditional API mocking implementation in Node.js looks roughly like this:

import http from 'node:http'

// Store the original request function.
const originalHttpRequest = http.request

// Override the request function entirely.
http.request = function (...args) {
  // Decide if the outgoing request matches a predicate.
  if (predicate(args)) {
    // If it does, never create a request, respond to it
    // using the mocked response from this blackbox.
    return coerceToResponse.bind(this, mock)
  }

  // Otherwise, construct the original request
  // and perform it as-is.
  return originalHttpRequest(...args)
}

The core philosophy of Interceptors is to run as much of the underlying network code as possible. Strange for a network mocking library, isn't it? Turns out, respecting the system's integrity and executing more of the network code leads to more resilient tests and also helps to uncover bugs in the code that would otherwise go unnoticed.

Interceptors heavily rely on class extension instead of function and module overrides. By extending the native network code, it can surgically insert the interception and mocking pieces only where necessary, leaving the rest of the system intact.

class XMLHttpRequestProxy extends XMLHttpRequest {
  async send() {
    // Call the request listeners and see if any of them
    // returns a mocked response for this request.
    const mockedResponse = await waitForRequestListeners({ request })

    // If there is a mocked response, use it. This actually
    // transitions the XMLHttpRequest instance into the correct
    // response state (below is a simplified illustration).
    if (mockedResponse) {
      // Handle the response headers.
      this.request.status = mockedResponse.status
      this.request.statusText = mockedResponse.statusText
      this.request.responseUrl = mockedResponse.url
      this.readyState = 2
      this.trigger('readystatechange')

      // Start streaming the response body.
      this.trigger('loadstart')
      this.readyState = 3
      this.trigger('readystatechange')
      await streamResponseBody(mockedResponse)

      // Finish the response.
      this.trigger('load')
      this.trigger('loadend')
      this.readyState = 4
      return
    }

    // Otherwise, perform the original "XMLHttpRequest.prototype.send" call.
    return super.send(...args)
  }
}

The request interception algorithms differ dramatically based on the request API. Interceptors acommodate for them all, bringing the intercepted requests to a common ground—the Fetch API Request instance. The same applies for responses, where a Fetch API Response instance is translated to the appropriate response format.

This library aims to provide full specification compliance with the APIs and protocols it extends.

What this library does

This library extends the following native modules:

  • http.get/http.request
  • https.get/https.request
  • XMLHttpRequest
  • fetch
  • WebSocket

Once extended, it intercepts and normalizes all requests to the Fetch API Request instances. This way, no matter the request source (http.ClientRequest, XMLHttpRequest, window.Request, etc), you always get a specification-compliant request instance to work with.

You can respond to the intercepted HTTP request by constructing a Fetch API Response instance. Instead of designing custom abstractions, this library respects the Fetch API specification and takes the responsibility to coerce a single response declaration to the appropriate response formats based on the request-issuing modules (like http.OutgoingMessage to respond to http.ClientRequest, or updating XMLHttpRequest response-related properties).

What this library doesn't do

  • Does not provide any request matching logic;
  • Does not handle requests by default.

Getting started

npm install @mswjs/interceptors

Interceptors

To use this library you need to choose one or multiple interceptors to apply. There are different interceptors exported by this library to spy on respective request-issuing modules:

  • ClientRequestInterceptor to spy on http.ClientRequest (http.get/http.request);
  • XMLHttpRequestInterceptor to spy on XMLHttpRequest;
  • FetchInterceptor to spy on fetch.

Use an interceptor by constructing it and attaching request/response listeners:

import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'

const interceptor = new ClientRequestInterceptor()

// Enable the interception of requests.
interceptor.apply()

// Listen to any "http.ClientRequest" being dispatched,
// and log its method and full URL.
interceptor.on('request', ({ request, requestId }) => {
  console.log(request.method, request.url)
})

// Listen to any responses sent to "http.ClientRequest".
// Note that this listener is read-only and cannot affect responses.
interceptor.on(
  'response',
  ({ response, isMockedResponse, request, requestId }) => {
    console.log('response to %s %s was:', request.method, request.url, response)
  }
)

All HTTP request interceptors implement the same events:

  • request, emitted whenever a request has been dispatched;
  • response, emitted whenever any request receives a response.

Using multiple interceptors

You can combine multiple interceptors to capture requests from different request-issuing modules at once.

import { BatchInterceptor } from '@mswjs/interceptors'
import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'
import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'

const interceptor = new BatchInterceptor({
  name: 'my-interceptor',
  interceptors: [
    new ClientRequestInterceptor(),
    new XMLHttpRequestInterceptor(),
  ],
})

interceptor.apply()

// This "request" listener will be called on both
// "http.ClientRequest" and "XMLHttpRequest" being dispatched.
interceptor.on('request', listener)

Note that you can use pre-defined presets that cover all the request sources for a given environment type.

Presets

When using BatchInterceptor, you can provide a pre-defined preset to its "interceptors" option to capture all request for that environment.

Node.js preset

This preset combines ClientRequestInterceptor, XMLHttpRequestInterceptor and is meant to be used in Node.js.

import { BatchInterceptor } from '@mswjs/interceptors'
import nodeInterceptors from '@mswjs/interceptors/presets/node'

const interceptor = new BatchInterceptor({
  name: 'my-interceptor',
  interceptors: nodeInterceptors,
})

interceptor.apply()

interceptor.on('request', listener)

Browser preset

This preset combines XMLHttpRequestInterceptor and FetchInterceptor and is meant to be used in a browser.

import { BatchInterceptor } from '@mswjs/interceptors'
import browserInterceptors from '@mswjs/interceptors/presets/browser'

const interceptor = new BatchInterceptor({
  name: 'my-interceptor',
  interceptors: browserInterceptors,
})

interceptor.on('request', listener)

Introspecting requests

All HTTP request interceptors emit a "request" event. In the listener to this event, they expose a request reference, which is a Fetch API Request instance.

There are many ways to describe a request in Node.js but this library coerces different request definitions to a single specification-compliant Request instance to make the handling consistent.

interceptor.on('request', ({ request, requestId, controller }) => {
  console.log(request.method, request.url)
})

Since the exposed request instance implements the Fetch API specification, you can operate with it just as you do with the regular browser request. For example, this is how you would read the request body as JSON:

interceptor.on('request', async ({ request, requestId }) => {
  const json = await request.clone().json()
})

Do not forget to clone the request before reading its body!

Modifying requests

Request representations are readonly. You can, however, mutate the intercepted request's headers in the "request" listener:

interceptor.on('request', ({ request }) => {
  request.headers.set('X-My-Header', 'true')
})

This restriction is done so that the library wouldn't have to unnecessarily synchronize the actual request instance and its Fetch API request representation. As of now, this library is not meant to be used as a full-scale proxy.

Mocking responses

Although this library can be used purely for request introspection purposes, you can also affect request resolution by responding to any intercepted request within the "request" event.

Access the controller object from the request event listener arguments and call its controller.respondWith() method, providing it with a mocked Response instance:

interceptor.on('request', ({ request, controller }) => {
  controller.respondWith(
    new Response(
      JSON.stringify({
        firstName: 'John',
        lastName: 'Maverick',
      }),
      {
        status: 201,
        statusText: 'Created',
        headers: {
          'Content-Type': 'application/json',
        },
      }
    )
  )
})

We use Fetch API Response class as the middle-ground for mocked response definition. This library then coerces the response instance to the appropriate response format (e.g. to http.OutgoingMessage in the case of http.ClientRequest).

The Response class is built-in in since Node.js 18. Use a Fetch API-compatible polyfill, like node-fetch, for older versions of Node.js.`

Note that a single request can only be handled once. You may want to introduce conditional logic, like routing, in your request listener but it's generally advised to use a higher-level library like Mock Service Worker that does request matching for you.

Requests must be responded to within the same tick as the request listener. This means you cannot respond to a request using setTimeout, as this will delegate the callback to the next tick. If you wish to introduce asynchronous side-effects in the listener, consider making it an async function, awaiting any side-effects you need.

// Respond to all requests with a 500 response
// delayed by 500ms.
interceptor.on('request', async ({ controller }) => {
  await sleep(500)
  controller.respondWith(new Response(null, { status: 500 }))
})

Mocking response errors

You can provide an instance of Response.error() to error the pending request.

interceptor.on('request', ({ request, controller }) => {
  controller.respondWith(Response.error())
})

This will automatically translate to the appropriate request error based on the request client that issued the request. Use this method to produce a generic network error.

Note that the standard Response.error() API does not accept an error message.

Mocking errors

Use the controller.errorWith() method to error the request.

interceptor.on('request', ({ request, controller }) => {
  controller.errorWith(new Error('reason'))
})

Unlike responding with Response.error(), you can provide an exact error reason to use to .errorWith(). Use this method to error the request.

Note that it is up to the request client to respect your custom error. Some clients, like ClientRequest will use the provided error message, while others, like fetch, will produce a generic TypeError: failed to fetch responses. Interceptors will try to preserve the original error in the cause property of such generic errors.

Observing responses

You can use the "response" event to transparently observe any incoming responses in your Node.js process.

interceptor.on(
  'response',
  ({ response, isMockedResponse, request, requestId }) => {
    // react to the incoming response...
  }
)

Note that the isMockedResponse property will only be set to true if you resolved this request in the "request" event listener using the controller.respondWith() method and providing a mocked Response instance.

Error handling

By default, all unhandled exceptions thrown within the request listener are coerced to 500 error responses, emulating those exceptions occurring on the actual server. You can listen to the exceptions by adding the unhandledException listener to the interceptor:

interceptor.on(
  'unhandledException',
  ({ error, request, requestId, controller }) => {
    console.log(error)
  }
)

To opt out from the default coercion of unhandled exceptions to server responses, you need to either:

  1. Respond to the request with a mocked response (including error responses);
  2. Propagate the error up by throwing it explicitly in the unhandledException listener.

Here's an example of propagating the unhandled exception up:

interceptor.on('unhandledException', ({ error }) => {
  // Now, any unhandled exception will NOT be coerced to a 500 error response,
  // and instead will be thrown during the process execution as-is.
  throw error
})

WebSocket interception

You can intercept a WebSocket communication using the WebSocketInterceptor class.

Important

This library only supports intercepting WebSocket connections created using the global WHATWG WebSocket class. Third-party transports, such as HTTP/XHR polling, are not supported by design due to their contrived nature.

import { WebSocketInterceptor } from '@mswjs/interceptors/WebSocket'

const interceptor = new WebSocketInterceptor()

Unlike the HTTP-based interceptors that share the same request/response events, the WebSocket interceptor only emits the connection event and let's you handle the incoming/outgoing events in its listener.

Important defaults

  1. Intercepted WebSocket connections are not opened. To open the actual WebSocket connection, call server.connect() in the interceptor.
  2. Once connected to the actual server, the outgoing client events are forwarded to that server by default. If you wish to prevent a client message from reaching the server, call event.preventDefault() for that client message event.
  3. Once connected to the actual server, the incoming server events are forwarded to the client by default. If you wish to prevent a server message from reaching the client, call event.preventDefault() for the server message event.
  4. Once connected to the actual server, the close event received from that server is forwarded to the client by default. If you wish to prevent that, call event.preventDefault() for that close event of the server.

WebSocket connection

Whenever a WebSocket instance is constructed, the connection event is emitted on the WebSocket interceptor.

intereceptor.on('connection', ({ client }) => {
  console.log(client.url)
})

The connection event exposes the following arguments:

Name Type Description
client WebSocketClientConnection An object representing a connected WebSocket client instance.
server WebSocketServerConnection An object representing the original WebSocket server connection.
info object Additional WebSocket connection information (like the original client protocols).

WebSocketClientConnection

.addEventListener(type, listener)

  • type, string
  • listener, EventListener

Adds an event listener to the given event type of the WebSocket client.

interface WebSocketServerConnectionEventMap {
  // Dispatched when the WebSocket client sends data.
  message: (this: WebSocket, event: MessageEvent<WebSocketData>) => void

  // Dispatched when the WebSocket client is closed.
  close: (this: WebSocket, event: CloseEvent) => void
}
client.addEventListener('message', (event) => {
  console.log('outgoing:', event.data)
})

.removeEventListener(type, listener)

  • type, string
  • listener, EventListener

Removes the listener for the given event type.

.send(data)

  • data, string | Blob | ArrayBuffer

Sends the data to the intercepted WebSocket client.

client.send('text')
client.send(new Blob(['blob']))
client.send(new TextEncoder().encode('array buffer'))

.close(code, reason)

Closes the client connection. Unlike the regular WebSocket.prototype.close(), the client.close() method can accept a non-configurable status codes, such as 1001, 1003, etc.

// Gracefully close the connection with the
// intercepted WebSocket client.
client.close()
// Terminate the connection by emulating
// the server unable to process the received data.
client.close(1003)

WebSocketServerConnection

.connect()

Establishes the connection to the original WebSocket server. Connection cannot be awaited. Any data sent via server.send() while connecting is buffered and flushed once the connection is open.

.addEventListener(type, listener)

  • type, string
  • listener, EventListener

Adds an event listener to the given event type of the WebSocket server.

interface WebSocketServerConnectionEventMap {
  // Dispatched when the server connection is open.
  open: (this: WebSocket, event: Event) => void

  // Dispatched when the server sends data to the client.
  message: (this: WebSocket, event: MessageEvent<WebSocketData>) => void

  // Dispatched when the server connection closes.
  close: (this: WebSocket, event: CloseEvent) => void
}
server.addEventListener('message', (event) => {
  console.log('incoming:', event.data)
})

.removeEventListener(type, listener)

  • type, string
  • listener, EventListener

Removes the listener for the given event type.

.send(data)

  • data, string | Blob | ArrayBuffer

Sends the data to the original WebSocket server. Useful in a combination with the client-sent events forwarding:

client.addEventListener('message', (event) => {
  server.send(event.data)
})

.close()

Closes the connection with the original WebSocket server. Unlike client.close(), closing the server connection does not accept any arguments and always asumes a graceful closure. Sending data via server.send() after the connection has been closed will have no effect.

API

Interceptor

A generic class implemented by all interceptors. You do not interact with this class directly.

class Interceptor {
  // Applies the interceptor, enabling the interception of requests
  // in the current process.
  apply(): void

  // Listens to the public interceptor events.
  // For HTTP requests, these are "request' and "response" events.
  on(event, listener): void

  // Cleans up any side-effects introduced by the interceptor
  // and disables the interception of requests.
  dispose(): void
}

For public consumption, use interceptors instead.

BatchInterceptor

Applies multiple request interceptors at the same time.

import { BatchInterceptor } from '@mswjs/interceptors'
import nodeInterceptors from '@mswjs/interceptors/presets/node'

const interceptor = new BatchInterceptor({
  name: 'my-interceptor',
  interceptors: nodeInterceptors,
})

interceptor.apply()

interceptor.on('request', ({ request, requestId }) => {
  // Inspect the intercepted "request".
  // Optionally, return a mocked response.
})

Using the /presets/node interceptors preset is the recommended way to ensure all requests get intercepted, regardless of their origin.

RemoteHttpInterceptor

Enables request interception in the current process while delegating the response resolution logic to the parent process. Requires the current process to be a child process. Requires the parent process to establish a resolver by calling the createRemoteResolver function.

// child.js
import { RemoteHttpInterceptor } from '@mswjs/interceptors/RemoteHttpInterceptor'
import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'

const interceptor = new RemoteHttpInterceptor({
  // Alternatively, you can use presets.
  interceptors: [new ClientRequestInterceptor()],
})

interceptor.apply()

process.on('disconnect', () => {
  interceptor.dispose()
})

You can still listen to and handle any requests in the child process via the request event listener. Keep in mind that a single request can only be responded to once.

RemoteHttpResolver

Resolves an intercepted request in the given child process. Requires for that child process to enable request interception by calling the createRemoteInterceptor function.

// parent.js
import { spawn } from 'child_process'
import { RemoteHttpResolver } from '@mswjs/interceptors/RemoteHttpInterceptor'

const appProcess = spawn('node', ['app.js'], {
  stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
})

const resolver = new RemoteHttpResolver({
  process: appProcess,
})

resolver.on('request', ({ request, requestId }) => {
  // Optionally, return a mocked response
  // for a request that occurred in the "appProcess".
})

resolver.apply()

Special mention

The following libraries were used as an inspiration to write this low-level API:

interceptors's People

Contributors

95th avatar alexk111 avatar atti187 avatar avivasyuta avatar blaenk avatar daniellehuisman avatar dependabot[bot] avatar gribnoysup avatar hamishhossack avatar hpohlmeyer avatar kalopilato avatar kettanaito avatar lauraseidler avatar leonardodino avatar marcosvega91 avatar mathanpec avatar mattcosta7 avatar mikicho avatar msutkowski avatar oscard0m avatar phryneas avatar raon0211 avatar rrogowski avatar scwr avatar sean-hernon avatar simmzl avatar tanem avatar thomas-p-wilson avatar tommos0 avatar tonykhaov 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

interceptors's Issues

xhr.upload is null

I've got code that looks like this:

const xhr = new XMLHttpRequest()
xhr.upload.addEventListener("progress", (e) => {
  // ...
})

When I run my test I get this error:

    TypeError: Cannot read property 'addEventListener' of null

      40 |         const xhr = new XMLHttpRequest()
      41 |         console.log(xhr)
    > 42 |         xhr.upload.addEventListener("progress", (e) => {

That console.log reveals that the upload property of the class is set to null.

   XMLHttpRequestOverride {
      upload: null
    }

If someone points me in the right direction, I'd be open to filling in this gap.

Support "x-msw-bypass" request header

Current behavior

Request with the x-msw-bypass header is matched against the request middleware. This means that the bypass header does not affect the mocking behavior as it should.

Expected behavior

Whenever encountered a request with the x-msw-bypass header, there must be no matching against the request middleware. Such request should be performed as-is, regardless if there's a middleware that can handle it.

Motivation

To preserve the same behavior present in the ctx.fetch() in MSW.

Cloning request options with a [Object: null prototype] value throws an exception

When cloning an object like this:

cloneObject({
  key: Object.create(null)
})

The internal cloneObject function throws the following exception:

TypeError: Cannot read property 'name' of undefined

Objects with null prototype don't have a constructor. The cloneObject function must check the constructor's existence before accessing it.

NextJS: The "superCtor" argument must be of type function

Hi, I am using msw with nextjs and I came across an error when working on the api route. The error log points to this library so I'm here.

Steps to reproduce

here is a minimal repo: https://github.com/dpyzo0o/next-msw-bug-demo

  1. yarn dev, which enables msw. Then try to click download image, the first time it will probably succeed (sometimes it also fails the first time). Then do a hard refresh of the page and click download image again. This time it will error out, the error message shown below points to node-request-interceptor

Screen Shot 2020-08-19 at 17 16 28

  1. If you build the app with yarn build && yarn start, which basically disables msw, it will never throw this error and everything works.

I'd really appreciate it if you could help me.

Two concurrent requests to the same host override each other

Steps to reproduce

http.request('http://httpbin.org/get')
http.request('http://httpbin.org/get', { headers: { 'foo': 'bar' } })

Current behavior

When accessed in the middleware function, the second request will not have the headers present, as the first request interferes with it.

Expected behavior

Each of the hosts must be processed separately.

Clues

  • Request identity is not respected
  • Watch out for mutable entities, make sure they never persist between requests

TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"

Error Message

env

  • Node 14
  • testing react application using msw, msw/node.

messages

    TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"

      at Object.proxiedOriginalRequest (node_modules/node-request-interceptor/lib/http/override.js:55:36)
      at ClientRequestOverride.<anonymous> (node_modules/node-request-interceptor/lib/http/ClientRequest/ClientRequestOverride.js:244:39)
      at step (node_modules/node-request-interceptor/lib/http/ClientRequest/ClientRequestOverride.js:33:23)
      at Object.next (node_modules/node-request-interceptor/lib/http/ClientRequest/ClientRequestOverride.js:14:53)
      at fulfilled (node_modules/node-request-interceptor/lib/http/ClientRequest/ClientRequestOverride.js:5:58)

Screen Shot 2020-08-16 at 0 57 25

Why this happen?

This happens when mock https request.

This error occurs because there is no cert property in options.
so options.protocol always set as http:

https://github.com/mswjs/node-request-interceptor/blob/master/src/utils/getUrlByRequestOptions.ts#L22-L23

    console.log(options); // No 'cert' property in `options` object.

    // Assume HTTPS if using an SSL certificate.
    options.protocol = options.cert ? 'https:' : DEFAULT_PROTOCOL

Attachment

console.log(options) result

Possible Solution

I think, options.protocol value should be decided by options.uri.protocol === 'https:'

Example, Reproduce

https://github.com/gwanduke/example-node-request-interceptor-issue-45

App.js

...
    fetch('https://example.com')
...

App.test.js

setupServer(
  rest.get('https://example.com', (req, res, ctx) => {
    return res(
      ctx.json({})
    );
  })
);

test('render', async () => {
    render(<App />);
});

Intercepting all requests

Hello,

I'm trying to intercept all network requests on a web app. I'd like to record everything that shows up in the Networks tab in the browser developer tools (except for web sockets).

I've added interceptors to my web app:

const interceptor = createInterceptor({
    modules: browserInterceptors,
    resolver(req, ref) {
        console.log(req.url);

        console.log(ref);
    },
});

interceptor.apply();

I have 2 buttons that make requests:

<button
    onClick={async () => {
        const data = await fetch(
            'https://pokeapi.co/api/v2/pokemon/1'
        );
        const json = await data.json();
        console.info({ json });
    }}
>
    Fetch Request
</button>
<button
    onClick={async () => {
        const oReq = new XMLHttpRequest();
        oReq.open('GET', 'https://pokeapi.co/api/v2/pokemon/1');
        oReq.send();
    }}
>
    XHR
</button>

Problems

  1. The XHR request interceptor is working. The fetch isn't. I'm getting this error for fetch: TypeError: Failed to execute 'json' on 'Response': body stream already read
    • Looks like #113 is related?
  2. Other network requests that are not using XHR are not being intercepted (should they?).

Things I've checked

  1. Using the browserInteceptors module, fetch and XMLHttpRequest are being shimmed. I checked this by running fetch and XMLHttpRequest in the console and the output was not XMLHttpRequest() { [native code] }.

Distribute interceptors properly

Expected behavior

  • Exported under node-request-interceptor/interceptors/[name]
  • Distributed in a format that would allow them to be imported from both ESM and CJS context (currently importing an interceptor directly from a ESM context doesn't compile, it strips away named export of SocketPolyfill).

TypeError: Cannot read property 'URL' of null

This error seems to be coming from node-request-interceptor while I'm running my tests with msw

(node:183) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'URL' of null
    at new XMLHttpRequest (/app/node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:113:42)
    at /app/node_modules/node-request-interceptor/lib/XMLHttpRequest/XMLHttpRequest/createXMLHttpRequestOverride.js:211:45

That line is marked by HERE below:

module.exports = function createXMLHttpRequest(window) {
  class XMLHttpRequest extends XMLHttpRequestEventTarget.interface {
    constructor() { // eslint-disable-line constructor-super
      const theThis = Object.create(new.target.prototype);
      XMLHttpRequestEventTarget.setup(theThis);
      theThis.upload = XMLHttpRequestUpload.create();
      theThis.upload._ownerDocument = window.document;

      theThis[xhrSymbols.flag] = {
        synchronous: false,
        withCredentials: false,
        mimeType: null,
        auth: null,
        method: undefined,
        responseType: "",
        requestHeaders: {},
        referrer: theThis._ownerDocument.URL,      // <----------------- HERE
        uri: "",
        timeout: 0,
        body: undefined,
        formData: false,
        preflight: false,
        requestManager: theThis._ownerDocument._requestManager,
        strictSSL: window._resourceLoader._strictSSL,
        proxy: window._resourceLoader._proxy,
        cookieJar: theThis._ownerDocument._cookieJar,
        encoding: theThis._ownerDocument._encoding,
        origin: theThis._ownerDocument.origin,
        userAgent: window.navigator.userAgent
      };

Is there something I should do differently to avoid this?

XMLHttpRequest: Ensure events order according to the specification

Current behavior

Some of the events are grouped together, not representing the valid flow of an XHR request:

https://github.com/mswjs/node-request-interceptor/blob/50e5aa72ee102a8bec8e2f38e3efc9efa26d5e74/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts#L288-L290

Expected behavior

The XMLHttpRequest events must be dispatched in the order described in the specification to guarantee compatibility for the users.

Running into issues with Got and another

Hey, so working getting a mock backend of sorts setup. Currently, I'm using Got and node-request-interceptor, all on the latest versions. The first issue I'm running into here is not being able to find the withDefaultInterceptors module in node-request-interceptor/presets/default, I get the error Cannot find module 'node-request-interceptor/presets/default' or its corresponding type declarations.. However, if I change the path to node-request-interceptor/lib/presets/default, no more errors are thrown and it finds the module. Maybe I'm doing something wrong here, not sure. Here's the the mock backend -

import { RequestInterceptor } from 'node-request-interceptor';
// import withDefaultInterceptors from 'node-request-interceptor/lib/presets/default'; // this bad
import withDefaultInterceptors from 'node-request-interceptor/lib/presets/default'; // this good
import { apiUrl } from '../config.json';

export default class MockBackend {
    public logger;
    public interceptor;

    constructor(logger) {
        this.logger = logger;
        this.interceptor = new RequestInterceptor(withDefaultInterceptors);
    }

    intercept() {
        this.interceptor.use((req) => {
            if ([apiUrl].includes(req.url.origin)) {
                return {
                    status: 301,
                    headers: {
                        'x-powered-by': 'node-request-interceptor',
                    },
                    body: JSON.stringify({
                        message: 'Hey, I am a mocked response',
                    }),
                }
            }
        })
    }

}

Anywho, next issue, whenever I'm using Got, it throws an error,

(node:8924) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "timeout" argument must be of type number. Received an instance of Object
    at validateNumber (internal/validators.js:122:11)
    at getTimerDuration (internal/timers.js:376:3)
    at new ClientRequest (_http_client.js:167:20)
    at request (https.js:314:10)
    at Object.proxiedOriginalRequest (C:\Users\Lyon\Documents\GitHub\squadops-discordbot-v5\node_modules\node-request-interceptor\lib\interceptors\ClientRequest\index.js:54:36)
    at ClientRequestOverride.<anonymous> (C:\Users\Lyon\Documents\GitHub\squadops-discordbot-v5\node_modules\node-request-interceptor\lib\interceptors\ClientRequest\ClientRequestOverride.js:235:39)
    at step (C:\Users\Lyon\Documents\GitHub\squadops-discordbot-v5\node_modules\node-request-interceptor\lib\interceptors\ClientRequest\ClientRequestOverride.js:33:23)
    at Object.next (C:\Users\Lyon\Documents\GitHub\squadops-discordbot-v5\node_modules\node-request-interceptor\lib\interceptors\ClientRequest\ClientRequestOverride.js:14:53)
    at fulfilled (C:\Users\Lyon\Documents\GitHub\squadops-discordbot-v5\node_modules\node-request-interceptor\lib\interceptors\ClientRequest\ClientRequestOverride.js:5:58)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
(node:8924) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhan
dled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)

I have no idea why it does this, clearly. Here is the file relating that calls this -

import { apiUrl } from '../config.json';
import got from 'got';

export default class OpsService {
    public logger;
    public path = apiUrl;

    constructor(logger: any) {
        this.logger = logger;

    }

    async getSession(sess_id) {
        const requestPath = `${this.path}/sessions/${sess_id}`;
        this.logger.warn('Ops Service',`Executing call to ${requestPath}`);
        const body = await got.get(requestPath);
        console.log(body);
    }
}

requestPath is valid, otherwise Got would have thrown an error. Kind of clueless here as to why it throws this error. Any help would be much appreciated.

ClientRequestOverride.end() does not accept chunk/encoding

Discovered in mswjs/msw#275

Current behavior

.end() method does not accept arguments like chunk/encoding according to the NodeJS specification.

Because of this, request issuing libraries that post a request body via req.end(body) are failing when it comes to performing an original request (request body is never written, as req.write() is never called).

Expected behavior

.end() behaves according to the aforementioned specification, meaning:

  1. It accepts an optional chunk argument. Appends that chunk to the existing request body.
  2. It accepts an optional encoding argument. Propagates that encoding to the original req.end() call.

Improve debugging experience

Why

Current build setup produces JavaScript artifacts that are hard to debug (compile code is not readable).

How

The build process should be minimal (stripping off type declarations). Code transformation shouldn't be needed, as the target version of node (>=10) should support all the features used in the library's source code.

Respect "username" and "password" when constructing a URL from RequestOptions

What

When constructing a URL instance out of RequestOptions we need to respect options.username and options.password to be set in the respective properties of the URL instance.

Why

To propagate the authentication data to the URL instance.

How

  • Introduce a separate getUrlByRequestOptions utility.
  • Map the username and password properties to the created URL instance.

Original HTTPS request misses the "url" request option

Current behavior

Given an HTTPS request constructed like this:

https.request(new URL('...'), { ... })

The request's URL is never set on the original call to https.request:

if (url.protocol === 'https:') {
request = pureMethod(options)

The normalized options in this case contain the url property, which is an unknown property for the function call.

The presence of the unexpected "url" property may be related to how the request options are constructed in the used library (@auth0/nextjs-auth0). This library does not append the "url" property on the request options.

Expected behavior

The original HTTPS request is performed properly, its request's URL is preserved.

This can be achieved the same way the original HTTP requests are handled:

request = pureMethod(url.toString(), options)

By providing the first explicit option (stringified request URL due to the incompatibility between the standard URL class and the import('url').URL that https module relies on), and passing the rest of the request options as the second argument.

node-request-interceptor doesn't play nicely with aws-sdk when interceptor is enabled

👋 again (excited to be one of the first users of the low-level lib)

as part of my product, i'm trying to allow users to "lock" the state of their microservice, and cache the responses of any outbound requests. but during this "locking", i get in an intermediate state, where i need some requests to go through transparently (aws), but the rest to be intercepted (requests to e.g. our other services, public APIs, etc)

i have a feeling aws-sdk is doing something sketchy here, but when i just have the interceptor enabled, here's the error i get trying to allow the aws requests to go through (I have some matching logic that returns undefined for them):

(node:74163) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"
    at new ClientRequest (_http_client.js:120:11)
    at request (https.js:289:10)
    at Object.proxiedOriginalRequest (/Users/scottyjacobson/node_modules/node-request-interceptor/lib/interceptors/ClientRequest/index.js:54:36)
    at ClientRequestOverride.<anonymous> (/Users/scottyjacobson/node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:241:39)
    at step (/Users/scottyjacobson/node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:33:23)
    at Object.next (/Users/scottyjacobson/node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:14:53)
    at fulfilled (/Users/scottyjacobson/node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:5:58)
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
    at startup (internal/bootstrap/node.js:283:19)
(node:74163) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:74163) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

reproduction steps:

'use strict'

const { RequestInterceptor } = require('node-request-interceptor')
const withDefaultInterceptors = require('node-request-interceptor/lib/presets/default')
  .default

const interceptor = new RequestInterceptor(withDefaultInterceptors)


interceptor.use(() => {
    return undefined
});

const AWS = require('aws-sdk');
AWS.config.update({region:'us-east-1'});

const s3 = new AWS.S3();

s3.getObject({
    Bucket: 'this.bucket.dont.matter',
    Key: 'neither.does.this'
}).promise();

package.json:

{
  "name": "request-interceptor-59",
  "version": "1.0.0",
  "main": "index.js",
  "author": "",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "^2.759.0",
    "node-request-interceptor": "^0.5.1"
  }
}

i know there's 218937423894 moving pieces in the aws-sdk, and i wish i could get you a more isolated repro case, but the inner workings of aws-sdk's use of http vs https (and yours for that matter) is slightly out of my pay grade. happy to help hunt down the issue if you know what direction to go looking in!

thanks!
Scotty

XMLHttpRequest: getResponseHeader() is case sensitive

Hi all! I've had an issue testing an auth integration with MSW and traced it to case sensitivity in the getResponseHeader function when retrieving the header value. I've created a PR with more detail here: #89

It looks like the current implementation was intentional so I'm keen to hear more if my PR is a breaking change for anyone? Either way I hope there's some way around this!

React native - Can't find variable: Buffer

Hi all! I just have enough time to log the issue: this is something introduced by #75 ; Buffer is one of those global vars that don't exists in RN. This is not a critical issue, as even on the user's side a simple global.Buffer = bufferPolyfill will do to fix the issue. Another alternative is to relay on https://www.npmjs.com/package/buffer directly from this package

XMLHttpRequest: readystatechange listener is called twice for unhandled requests

Code

https://github.com/mswjs/node-request-interceptor/blob/b19c82db06fc9c92a8606bd02ea967a1391d7837/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts#L358-L364

See the workaround test:

https://github.com/mswjs/node-request-interceptor/blob/b19c82db06fc9c92a8606bd02ea967a1391d7837/test/compliance/XMLHttpRequest/events-order.test.ts#L75-L88

Expected behavior

The order of the XMLHttpRequest events must be this:

['loadstart', 1],
['readystatechange', 2],
['readystatechange', 4],
['load', 4],
['loadend', 4],

There must be no duplicate ['readystatechange', 4] event.

Why this is happening?

XMLHttpRequest polyfill from JSDOM executes the attached readystatechange listeners as a part of XMLHttpRequest life-cycle, which is correct. However, somehow the way that the listener is attached makes it being called twice.

Include request ID in `debug` calls

That way it's possible to narrow debugging per specific request. Useful when debugging parallel requests.

How

  1. Generate a reproducible request ID for req.url and the timestamp. Do this as soon as the all the prerequisites are known, so debugging is consistent and tight to the request ID through the library's execution.
  2. Create a custom debug() instance and use it everywhere.

multiple interceptors causing restore problem

We know that there is a problem when using multiple interceptors. restore function will remove monkey patches even if there are other interceptors. The last one is mswjs/msw#637

The solution of this problem is not really easy but what you think if we add a counter on the interceptor? I know that is not a complete solution but could be a first start

--- a/src/createInterceptor.ts
+++ b/src/createInterceptor.ts
@@ -63,12 +63,15 @@ export interface InterceptorApi {
   restore(): void
 }
 
+let interceptosCounter = 0
+
 export function createInterceptor(options: InterceptorOptions): InterceptorApi {
   const observer = new StrictEventEmitter<InterceptorEventsMap>()
   let cleanupFns: InterceptorCleanupFn[] = []
 
   return {
     apply() {
+      interceptosCounter++
       cleanupFns = options.modules.map((interceptor) => {
         return interceptor(observer, options.resolver)
       })
@@ -78,14 +81,16 @@ export function createInterceptor(options: InterceptorOptions): InterceptorApi {
     },
     restore() {
       observer.removeAllListeners()
-
+      interceptosCounter--
       if (cleanupFns.length === 0) {
         throw new Error(
           `Failed to restore patched modules: no patches found. Did you forget to run ".apply()"?`
         )
       }
 
-      cleanupFns.forEach((restore) => restore())
+      if (interceptosCounter === 0) {
+        cleanupFns.forEach((restore) => restore())
+      }
     },
   }
 }

axios requests, when using http adapter, can't handle response body as object

I'd been using axios just fine to basically build a much simpler nock-type interface using node-request-interceptor (fantastic lib btw! thanks for providing it), but then i realized that in my real tests, which will be consuming the interface i'm making, i'm using

  1. inside jest
  2. using axios.defaults.adapter = require('axios/lib/adapters/http');.

so once i remembered and i set that adapter line (2^), my code, which was working beautifully, started throwing:

TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type object

Here's a repro file, which throws unless you assign a string to body: or if you comment out the axios.defaults.adapter line.

'use strict';

const { RequestInterceptor } = require('node-request-interceptor');
const withDefaultInterceptors = require('node-request-interceptor/lib/presets/default').default;

const interceptor = new RequestInterceptor(withDefaultInterceptors);

function handleResponse(request) {
    return {
        status: 200,
        headers: {
            some: 'header'
        },
        body: {
            valueFromObject: 'thisWorkedWithTheOtherAxiosAdapter'
        }
    };
}

interceptor.use(handleResponse);

const axios = require('axios');
axios.defaults.adapter = require('axios/lib/adapters/http');

describe('Repo my test that is breaking', () => {
    it('Returns the expecting string error', async () => {
        const iWontGetHere = await axios('http://theurlshouldntmatter.com');

        console.log(iWontGetHere);
    });
});

Thank you in advance! I tried JSON.stringify()ing the object, but then it escapes the JSON characters and iWontGetHere.data becomes a string rather than the object i would be expecting

Allow access to original response in the middleware

Hey y'all 👋

First of all thanks for all the hard work on this library, it's nice to see this focused, low-level approach to intercepting requests.

I think there is one feature that is currently doesn't seem to be possible to achieve with this interceptor and I'm wondering if you would be interested in looking into solving this use-case.

I was about to use this lib to monitor outgoing requests of a node.js service, and what do you know there is even an example of this in the readme:

interceptor.use((req) => {
  // Will print to stdout any outgoing requests
  // without affecting their responses
  console.log('%s %s', req.method, req.url.href)
})

One issue here though, it would be valuable to also be able to monitor response statusCode on response end, but there is no way to hook up to original response: req is not a real request instance, it's just normalized request options, so there is no way to attach 'response' event listener, and the second argument ref is the mocked response instance, not the original one (which makes sense, but doesn't help with the issue too).

Effectively if you don't want to use normalized req to send it yourself (and it's currently missing a bunch of http.request options to do it safely) there is no way (as far as I see) to intercept incoming response. If not for monitoring, I can imagine this being useful for e.g., recording and replaying functionality, where you would actually want to conditionally either bypass the interceptor if there are no recordings or respond immediately with with a record if it exists.

One idea of the top of my head is to pass the instance of the ClientRequest to the middleware handler:

export type RequestMiddleware = (
  req: InterceptedRequest,
  ref: IncomingMessage | XMLHttpRequest,
  req: http.ClientRequest,
) => ReturnedResponse | Promise<ReturnedResponse>

This would allow end users of the library to subscribe to response event that will allow them to access original response info.

I'm happy to implement it, but wanted to check first if it's something that aligns with your vision of this library and get your thoughts on the subject.

XMLHttpRequest does not inherit the "timeout" option

When intercepting an XMLHttpRequest instance, NRI doesn't respect its timeout option. This causes tools that utilize timeout to fail, as performing such requests originally omits the previously assigned timeout.

EXTERNAL_TOOL (timeout) -> NRI -> captured, perform original -> original (NO timeout)

Expected behavior

XMLHttpRequest instances inherit and propagate the timeout property. This only affects XHR issues in async mode.

Wrong response, when responseType equals "json"

Hello

I use mock-service-worker for writing tests in my application and I faced a problem. My application use rxjs/ajax for making network request and rxjs/ajax expects xhrRequest.response to be a javascript object, when xhrRequest.responseType equals "json"(this behavior is described here https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response), but mock-service-worker, that using node-request-interceptor, returns xhrRequest.response type of string.

This behavior makes my application work differently in tests and in the browser. Is this behavior a bug in node-request-interceptor?

interceptor.on('response'...) not catching any events

i have the following code in my project:

const interceptor = new (createInterceptor as any)({
    modules: nodeInterceptors,
});
interceptor.apply();

interceptor.on('request', (request: IsomorphicRequest) => {
    this.logger.log(
        '=============request============\n\n%s\n\n========================',
        JSON.stringify(request)
    );
});
interceptor.on(
    'response',
    (req: IsomorphicRequest, res: IsomorphicResponse) => {
        this.logger.log(
            '=============request============\n\n%s\n=============response============\n\n%s\n\n========================',
            JSON.stringify(req),
            JSON.stringify(res)
        );
    }
);

process.on('disconnect', () => {
    interceptor.restore();
});

the listener on request works fine, and logs every request. But, the listener on response is never logging anything. am i missing any setup/config?

Node request interceptor is getting skipped for the response interceptor

When attaching a debugger to the following code (with breakpoints set at the request and response interceptors):

export class NodeHttpClient implements Provider<AxiosLike> {
	value() {
		const interceptor = createInterceptor({
			modules: nodeInterceptors,
			resolver(request) {},
		});

		interceptor.on('request', (request) => {
			console.log(request);
			return request;
		});

		interceptor.on('response', (request, response) => {
                        console.log(request, response);
                        return response;
		});

		const instance = axios.create();
		instance.defaults.adapter = require('axios/lib/adapters/http');
		interceptor.apply();
		return instance;
	}
}

it appears as if the request interceptor is skipped and execution is started with the response interceptor when a request is initiated. I've tried this with other interceptor modules and they all behave the same way.

I'd appreciate any help looking into this or a direction to be pointed towards to resolve this, especially if it's something minor I'm overlooking.

Thank you!

React native jest runtime

Sorry about this, just an heads up on #37
This, while it correctly allow me to run msw on the application, it broke my tests. Apparently, while running my jest tests, the overrideModules.native.ts is also loaded, which is not the behaviour I was expecting.

I'll look more into this tonight.

Does not work well with jest 27

Jest 27 removes setImmediate, which is used in createClientRequestOverride.

Currently, I get the following in my tests:

ReferenceError: setImmediate is not defined
          at ClientRequestOverride.write (~/node_modules/@mswjs/interceptors/src/interceptors/ClientRequest/createClientRequestOverride.ts:124:7)
          at writeToStream (~/node_modules/node-fetch/lib/index.js:653:8)
          at ~/node_modules/node-fetch/lib/index.js:1627:3

Jest@27 compatibility

Issues

  • JSDOM is no longer the default test environment.
  • setImmediate is not defined (#123)
  • TypeError [ERR_INVALID_ARG_TYPE]: The "options.agent" property must be one of Agent-like Object, undefined, or false. Received an instance of Object
  • Test functions cannot both take a 'done' callback and return something. Either use a 'done' callback, or return a promise. (previously less strict).
  • ReferenceError: browserType.launch: setImmediate is not defined when running in-browser tests.

ClientRequest headers are not normalized

What

Current implementation proxies options.headers to the intercepted request instance:

https://github.com/mswjs/node-request-interceptor/blob/e743f0f41faf96d0a53c459ba09ddf99946bf547/src/http/ClientRequest/ClientRequestOverride.ts#L118-L123

Why

Different request issuing libraries format headers differently. For instance:

  • node-fetch capitalizes all headers names;
  • whatwg-fetch converts header names to start with lowercase.

This difference makes it problematic to properly asserts an intercepted request headers in tests.

How

Headers names must be formatted to start with lowercase.

Requests to relative URL fail

Steps to reproduce

  1. Perform a fetch('/login') request in Node-like environment.
  2. See the following error from the XMLHttpRequest class:
    TypeError: Invalid URL: /login

      2152 |             this.readyState = this.LOADING;
      2153 |             this.data = data || '';
    > 2154 |             const url = new URL(this.url);
           |                         ^
      2155 |             const req = {
      2156 |                 url: cleanUrl(url),
      2157 |                 method: this.method,

Getting "TypeError [ERR_INVALID_ARG_TYPE]: The "timeout" argument must be of type number. Received an instance of Object" when using "got"

When using msw on node for requests created by got I'll get the following error.

    at validateNumber (internal/validators.js:129:11)
    at getTimerDuration (internal/timers.js:381:3)
    at new ClientRequest (_http_client.js:170:20)
    at request (http.js:50:10)
    at Object.proxiedOriginalRequest (./node_modules/node-request-interceptor/src/interceptors/ClientRequest/index.ts:67:29)
    at ClientRequestOverride.<anonymous> (./node_modules/node-request-interceptor/src/interceptors/ClientRequest/ClientRequestOverride.ts:268:15)
    at step (./node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:33:23)
    at Object.next (./node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:14:53)
    at fulfilled (./node_modules/node-request-interceptor/lib/interceptors/ClientRequest/ClientRequestOverride.js:5:58)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

The initial requested created by me doesn't even contain a timeout value.
I've created a minimal repo to showcase the error: https://github.com/KnisterPeter/node-request-interceptor-with-got-issue

Socket.setNoDelay: TypeError: Cannot read property 'apply' of undefined

What

When using a supertest library there's the following exception:

TypeError: Cannot read property 'apply' of undefined
    at callSocketMethod (_http_client.js:708:27)
    at onSocket (_http_client.js:716:7)
    at ClientRequestOverride._deferToConnect (_http_client.js:725:5)
    at ClientRequestOverride.setNoDelay (_http_client.js:758:8)
    at Test.Request.request (/Users/kettanaito/Projects/contrib/msw-supertest/node_modules/superagent/lib/node/index.js:628:7)
    at Test.Request.end (/Users/kettanaito/Projects/contrib/msw-supertest/node_modules/superagent/lib/node/index.js:767:8)
    at Test.end (/Users/kettanaito/Projects/contrib/msw-supertest/node_modules/supertest/lib/test.js:125:7)
    at Object.<anonymous> (/Users/kettanaito/Projects/contrib/msw-supertest/index.js:29:4)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)

Why

The supertest has its own request abstraction, one of the steps of which calls req.socket.setNoDelay() method on the Socket instance of the request. The setNoDelay() method is not polyfilled by NRI, which causes an exception.

Node's _http_client.js may call arbitrary Socket methods during socket's lifecycle.

Always return full "MockedResponse" instance in the "response" event

Current type annotation is invalid, as Partial<MockedResponse> implies any of the mocked response properties could be missing. That is not true, as the library ensures all the mocked response properties are set (the values can still be undefined for properties like headers or body, but not for status or statusText).

ClientRequest patch is never restored

The following patch of the ClientRequest class doesn't seem to get restored anywhere:

http.ClientRequest = ClientRequestOverride

Solution

Restore the http.ClientRequest to the pureClientRequest (stored original class) in the cleanup function of interceptClientRequest:

return () => {
debug('restoring modules...')
for (const requestModule of pureModules.values()) {
requestModule.module.get = requestModule.get
requestModule.module.request = requestModule.request
}
pureModules.clear()
}

GitHub

Crash when response is empty

Hi,

Thanks for your work !

Since last version, when the response is empty my tests with msw crashes :
image
image
image

Is the response now mandatory ?

Regards,
Joccd

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.