bun icon indicating copy to clipboard operation
bun copied to clipboard

Support `msw`

Open xxleyi opened this issue 1 year ago • 20 comments

What is the problem this feature would solve?

use msw in bun has some problem, after some exploration, find node:http is not fully implemented in Bun.

with this code

import axios from 'axios';

import { setupServer } from 'msw/node'

const server = setupServer(...[])
server.listen({
  onUnhandledRequest: 'warn',
});

axios.get('https://swapi.dev/api/people/?page=2')
  .then(function (response) {
    // handle success
    console.log(response.data.results.length);
  })
  .catch(function (error) {
    // handle error
    console.log(error?.message);
  });

and dependencies:

{
  "dependencies": {
    "axios": "^1.4.0",
    "msw": "^1.2.3"
  }
}

output in Node:

[MSW] Warning: captured a request without a matching request handler:

  • GET https://swapi.dev:8001/api/people/

If you still wish to intercept this unhandled request, please create a request handler for it.
Read more: https://mswjs.io/docs/getting-started/mocks
Request failed with status code 400

output in Bun

Cannot call a class constructor without |new|

and first two of error stack is

TypeError: Cannot call a class constructor without |new|
      at ClientRequest (node:http:884:26)

What is the feature you are proposing to solve the problem?

fully implement node:http to support msw

What alternatives have you considered?

No response

xxleyi avatar Mar 04 '23 05:03 xxleyi

Thank you for reporting this.

Our node:http module is basically complete but it looks like some exports are missing (but implemented) and aside from that we have been getting a few bug reports with it lately.

I'm currently working on porting all of the node:http tests from Node and working through the bugs now and hopefully we can get full compatibility with node:http and node:https (or close to it) within the next few days or so.

ThatOneBro avatar Mar 06 '23 18:03 ThatOneBro

Thank you for reporting this.

Our node:http module is basically complete but it looks like some exports are missing (but implemented) and aside from that we have been getting a few bug reports with it lately.

I'm currently working on porting all of the node:http tests from Node and working through the bugs now and hopefully we can get full compatibility with node:http and node:https (or close to it) within the next few days or so.

Sorry to bother, do we have trouble in porting node:http?

xxleyi avatar May 22 '23 14:05 xxleyi

@Jarred-Sumner sorry to bother, can you have a look at this problem, which broke msw in bun:test

xxleyi avatar Jun 02 '23 06:06 xxleyi

Current output in bun:

➜ bun index.ts
🚀 ~ http: {
  Agent: [class Agent],
  Server: [class Server],
  createServer: [Function],
  ServerResponse: [class ServerResponse],
  IncomingMessage: [class IncomingMessage],
  request: [Function],
  get: [Function],
  maxHeaderSize: 16384,
  validateHeaderName: [Function],
  validateHeaderValue: [Function],
  setMaxIdleHTTPParsers: [Function: setMaxIdleHTTPParsers],
  globalAgent: { /* Removed for comparison to Node */ },
  ClientRequest: [class ClientRequest],
  OutgoingMessage: [class OutgoingMessage]
}

Node is here

{
  _connectionListener: [Function: connectionListener],
  Agent: [Function: Agent] { defaultMaxSockets: Infinity },
  ClientRequest: [Function: ClientRequest],
  IncomingMessage: [Function: IncomingMessage],
  OutgoingMessage: [Function: OutgoingMessage],
  Server: [Function: Server],
  ServerResponse: [Function: ServerResponse],
  createServer: [Function: createServer],
  validateHeaderName: [Function: __node_internal_],
  validateHeaderValue: [Function: __node_internal_],
  get: [Function: get],
  request: [Function: request],
  maxHeaderSize: [Getter],
  globalAgent: [Getter/Setter]
}

So the discrepancy is now that only Bun has: setMaxIdleHTTPParsers and only Node has _connectionListener, and that the types are somewhat different like maxHeaderSize is a 16384 in bun and a [Getter] in Node.

birkskyum avatar Aug 27 '23 16:08 birkskyum

The ticket here is specifically about ClientRequest and msw, which now is in Bun, so @xxleyi, can you verify that this works now? You can change the title to something more specific, like Support msw or ClientRequest not in node:http, and close it if it is working.

birkskyum avatar Aug 27 '23 16:08 birkskyum

The ticket here is specifically about ClientRequest and msw, which now is in Bun, so @xxleyi, can you verify that this works now? You can change the title to something more specific, like Support msw or ClientRequest not in node:http, and close it if it is working.

Original issue has been fixed, but Support msw still not works, I will change title to something more specific

xxleyi avatar Aug 28 '23 00:08 xxleyi

Issue in MSW repo:

  • https://github.com/mswjs/msw/issues/1718

birkskyum avatar Aug 29 '23 11:08 birkskyum

Some additional information about node.http module compatibility in BUN https://github.com/mswjs/interceptors/issues/418#issuecomment-1722383706

OleksandrKucherenko avatar Sep 18 '23 07:09 OleksandrKucherenko

Hey, folks. So from the report in https://github.com/mswjs/interceptors/issues/418#issuecomment-1722383706, it appears that this is no longer an issue with MSW 2.0. I highly encourage you updating and letting me know whether the same cannot call without new error persists on that version. Thanks.

kettanaito avatar Nov 11 '23 13:11 kettanaito

Hey, I try with a minimal repo like this

import { HttpResponse, http } from 'msw';
import { setupServer } from 'msw/node'

const handlers = [
    http.get("https://bun.sh", () => {
        return HttpResponse.json({ "test": "a" })
    })
]

export const server = setupServer(...handlers)

server.listen({
    onUnhandledRequest: "warn"
})

const response = await fetch("https://bun.sh");
const html = await response.text(); // HTML string

And give this error:

61 | }, getEventListeners = function(emitter, type) {
62 |   if (emitter instanceof EventTarget)
63 |     throwNotImplemented("getEventListeners with an EventTarget", 2678);
64 |   return emitter.listeners(type);
65 | 
66 | }, setMaxListeners = function(n, ...eventTargets) {
                                                             ^
TypeError: undefined is not a function
      at node:events:66:58
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:60:10
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:18:4
      at new Promise (:1:20)
      at __async (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:2:9)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/chunk-YQGTMMOZ.mjs:50:10
      at emitAsync (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/chunk-YQGTMMOZ.mjs:44:25)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:62:34
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:61:53
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@open-draft/until/lib/index.mjs:4:23

139 |   }
140 | };
141 | var FetchInterceptor = _FetchInterceptor;
142 | FetchInterceptor.symbol = Symbol("fetch");
143 | function createNetworkError(cause) {
144 |   return Object.assign(new TypeError("Failed to fetch"), {
                          ^
TypeError: Failed to fetch
      at createNetworkError (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:144:23)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:84:30

The undefined is not a function is a console log of the error in the /node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:84:30

The error is inside the bun implementation of setMaxListeners.

If I comment that function the library works like a charm

Nisgrak avatar Nov 13 '23 08:11 Nisgrak

Thanks to

@Nisgrak's tips

Hey, I try with a minimal repo like this

import { HttpResponse, http } from 'msw';
import { setupServer } from 'msw/node'

const handlers = [
    http.get("https://bun.sh", () => {
        return HttpResponse.json({ "test": "a" })
    })
]

export const server = setupServer(...handlers)

server.listen({
    onUnhandledRequest: "warn"
})

const response = await fetch("https://bun.sh");
const html = await response.text(); // HTML string

And give this error:

61 | }, getEventListeners = function(emitter, type) {
62 |   if (emitter instanceof EventTarget)
63 |     throwNotImplemented("getEventListeners with an EventTarget", 2678);
64 |   return emitter.listeners(type);
65 | 
66 | }, setMaxListeners = function(n, ...eventTargets) {
                                                             ^
TypeError: undefined is not a function
      at node:events:66:58
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:60:10
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:18:4
      at new Promise (:1:20)
      at __async (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/msw/lib/node/index.mjs:2:9)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/chunk-YQGTMMOZ.mjs:50:10
      at emitAsync (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/chunk-YQGTMMOZ.mjs:44:25)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:62:34
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:61:53
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@open-draft/until/lib/index.mjs:4:23

139 |   }
140 | };
141 | var FetchInterceptor = _FetchInterceptor;
142 | FetchInterceptor.symbol = Symbol("fetch");
143 | function createNetworkError(cause) {
144 |   return Object.assign(new TypeError("Failed to fetch"), {
                          ^
TypeError: Failed to fetch
      at createNetworkError (/Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:144:23)
      at /Users/enekorg/Proyectos/test/test-bun-msw/node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:84:30

The undefined is not a function is a console log of the error in the /node_modules/@mswjs/interceptors/lib/node/interceptors/fetch/index.mjs:84:30

The error is inside the bun implementation of setMaxListeners.

If I comment that function the library works like a charm

Here is the workaround(currently in Bun v1.0.18):

import { expect, test, describe, mock } from "bun:test"
// fix
mock.module("events", () => {
  return {
    setMaxListeners: () => {},
  }
})

import { HttpResponse, http } from 'msw';
import { setupServer } from 'msw/node'

const handlers = [
    http.get("https://bun.sh", () => {
        return HttpResponse.json({ "test": "a" })
    })
]

export const server = setupServer(...handlers)

server.listen({
    onUnhandledRequest: "warn"
})

const response = await fetch("https://bun.sh");
const html = await response.text(); // HTML string

rhyzx avatar Dec 19 '23 06:12 rhyzx

Does it only work with fetch()? I have the same example bellow, using Axios for the request, and it’s throwing an error:

import axios from "axios";
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";

const axiosInstance = axios.create();

const handlers = [
  http.get("https://bun.sh", () => {
    return HttpResponse.json({ test: "a" });
  }),
];

export const server = setupServer(...handlers);

server.listen({
  onUnhandledRequest: "warn",
});

// this works fine
// const response = await fetch("https://bun.sh");
// const html = response.json();

// but with axios, it doesn't
const response = await axiosInstance.get("https://bun.sh");
const html = response.data;
bun test v1.0.26 (c75e768a)

src/Sample.test.ts:
432 |         total: totalResponseBodyLength
433 |       });
434 |     };
435 |     if (response.body) {
436 |       this.logger.info("mocked response has body, streaming...");
437 |       const reader = response.body.getReader();
                           ^
TypeError: response.body.getReader is not a function. (In 'response.body.getReader()', 'response.body.getReader' is undefined)
      at respondWith (/Users/joeldaros/corteva/msw-2-issue/node_modules/@mswjs/interceptors/lib/node/chunk-IZG42TWK.mjs:437:22)

joel-daros avatar Feb 05 '24 12:02 joel-daros

Does it only work with fetch()? I have the same example bellow, using Axios for the request, and it’s throwing an error:

import axios from "axios";
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";

const axiosInstance = axios.create();

const handlers = [
  http.get("https://bun.sh", () => {
    return HttpResponse.json({ test: "a" });
  }),
];

export const server = setupServer(...handlers);

server.listen({
  onUnhandledRequest: "warn",
});

// this works fine
// const response = await fetch("https://bun.sh");
// const html = response.json();

// but with axios, it doesn't
const response = await axiosInstance.get("https://bun.sh");
const html = response.data;
bun test v1.0.26 (c75e768a)

src/Sample.test.ts:
432 |         total: totalResponseBodyLength
433 |       });
434 |     };
435 |     if (response.body) {
436 |       this.logger.info("mocked response has body, streaming...");
437 |       const reader = response.body.getReader();
                           ^
TypeError: response.body.getReader is not a function. (In 'response.body.getReader()', 'response.body.getReader' is undefined)
      at respondWith (/Users/joeldaros/corteva/msw-2-issue/node_modules/@mswjs/interceptors/lib/node/chunk-IZG42TWK.mjs:437:22)

I too facing the same issue. @joel-daros were you able to fix this issue?

kishores05 avatar Feb 13 '24 15:02 kishores05

too facing the same issue. @joel-daros were you able to fix this issue?

Nope. Maybe @kettanaito can help to identify if it’s a MSW or Bun issue.

joel-daros avatar Feb 13 '24 22:02 joel-daros

too facing the same issue. @joel-daros were you able to fix this issue?

Nope. Maybe @kettanaito can help to identify if it’s a MSW or Bun issue.

Adding below snippet in jest configuration worked for me.

const { Blob, File } = require('node:buffer')
const { fetch, Headers, FormData, Request, Response } = require('undici')
 
Object.defineProperties(globalThis, {
  fetch: { value: fetch, writable: true },
  Blob: { value: Blob },
  File: { value: File },
  Headers: { value: Headers },
  FormData: { value: FormData },
  Request: { value: Request },
  Response: { value: Response },
})

kishores05 avatar Feb 14 '24 04:02 kishores05

@joel-daros, I can help indeed, with this issue any other issues: MSW works without problems in Node.js. Every exception you encounter while using it with Bun is Bun's issue related to Node.js compatibility (it can also be your misconfigured environment, like @kishores05 has pointed out, so first ensure MSW works in your environment).

kettanaito avatar Feb 15 '24 13:02 kettanaito

Is there any progress on this?

boldurean avatar Apr 02 '24 09:04 boldurean

A heads-up on this, in the near future MSW will migrate to a new architecture for request interception in Node.js (see https://github.com/mswjs/interceptors/pull/515). That architecture will rely on net.Socket and HttpAgent instead of http.ClientRequest. If anyone from the Bun team is looking into this issue, consider looking at the compatibility with net.Socket in Node.js as the first step.

The new algorithm also relies on HTTPParser which is not a public API in Node.js but is distributed regardless in the node:_http_common built-in module. Bun has to implement both that module and the HTTPParser class in order for MSW to work in Bun.

kettanaito avatar Apr 02 '24 13:04 kettanaito

Does this still reproduce for you? using Bun 1.1.8 this works for me.

nektro avatar May 10 '24 22:05 nektro

Does this still reproduce for you? using Bun 1.1.8 this works for me.

yeah, still reproduce for me. did you try with the code demo i provide?

update: with msw v2 lastest version(2.3.0), the code demo works, however, if i put a real handler, still has issues:

import axios from 'axios';
import { http, passthrough, HttpResponse } from 'msw'

import { setupServer } from 'msw/node'

const server = setupServer(...[
  http.get('https://swapi.dev/api/people/', () => {
    // return passthrough()
    return HttpResponse.json({ results: [{}, {}] })
  }),
])
server.listen({
  onUnhandledRequest: 'warn',
});

axios.get('https://swapi.dev/api/people/?page=2')
  .then(function (response) {
    // handle success
    console.log(response.data.results.length);
  })
  .catch(function (error) {
    // handle error
    console.log(error?.message);
  });

with output:

485 |       writableFinished: { value: true },
486 |       writableEnded: { value: true }
487 |     });
488 |     this.emit("finish");
489 |     const { status, statusText, headers, body } = mockedResponse;
490 |     this.response.statusCode = status;
          ^
TypeError: Attempted to assign to readonly property.

xxleyi avatar May 11 '24 00:05 xxleyi

A heads-up on this, in the near future MSW will migrate to a new architecture for request interception in Node.js (see mswjs/interceptors#515). That architecture will rely on net.Socket and HttpAgent instead of http.ClientRequest. If anyone from the Bun team is looking into this issue, consider looking at the compatibility with net.Socket in Node.js as the first step.

The new algorithm also relies on HTTPParser which is not a public API in Node.js but is distributed regardless in the node:_http_common built-in module. Bun has to implement both that module and the HTTPParser class in order for MSW to work in Bun.

Looks like msw 2.4.4 is now using the HTTPParser which Bun doesn't currently seem to support (I'm on 1.1.27). Is there a Bun version that does, or should this issue be reopened?

silverAndroid avatar Sep 08 '24 19:09 silverAndroid

looks like https://github.com/oven-sh/bun/issues/13072 is already open

nektro avatar Sep 08 '24 21:09 nektro

Can confirm we've migrated to use built-in HTTP parser in Node.js since v2.4.4. If you wish to run MSW in Bun, Bun has to implement that parser. It may be tricky since it's technically an internal built-in _http_common.

kettanaito avatar Sep 09 '24 14:09 kettanaito