Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Native Msw And Axios Not Working Together #2026

Closed
batu0b opened this issue Feb 8, 2024 · 24 comments
Closed

React Native Msw And Axios Not Working Together #2026

batu0b opened this issue Feb 8, 2024 · 24 comments

Comments

@batu0b
Copy link

batu0b commented Feb 8, 2024

When I make requests to msw handlers, axios returns empty strings as data for some reason, but when I make requests with fetch, the data comes correctly. when I check if the handlers are triggered, the handlers are triggered. When I make a request to another api I get data with axios. can you help me to solve the problem?

index.js

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

async function enableMocking() {
  await import('./msw.polyfills');
  const {server} = await import('./src/mocks/server');
  server.listen();
}
enableMocking();
AppRegistry.registerComponent(appName, () => App);

server.js

import {setupServer} from 'msw/native';
import {handlers} from './handlers';

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

My Fetch Method:

const testFetch = async () => {
    try {
      const res = await axios.get('https://api.myMockApi.com/getAllProducts');
      const data = await res.data;
      console.log(data);
    } catch (error) {
      console.log(error);
    }
  };

handlers.js:

  http.get(`${url}/getAllProducts`, async ({}) => {
    console.log("test");
    return HttpResponse.json(marketData);
  }),

The Console Log Here Works Both In Fetch And In Axios

"axios": "^1.6.5"
 "react": "18.2.0",
"react-native": "0.73.4",
"fast-text-encoding": "^1.0.6",
"react-native-url-polyfill": "^2.0.0",
"msw": "^2.1.7",

Node Version : v18.13.0

@AhmedBHameed
Copy link

AhmedBHameed commented Feb 14, 2024

Not sure If I have the same issue but I'm encounter issue with nest.js module "@nestjs/axios".

This is some info about the console error.

  ● Notification controller › Notification controller in test mode › should call POST /api/notifications IN TEST MODE with type of LIVE_SESSION successfully

    TypeError: Cannot read properties of null (reading 'readable')



  ● Notification controller › Notification controller in test mode › should call POST /api/notifications IN TEST MODE with type of LIVE_SESSION successfully

    TypeError: body.getReader is not a function

      at _NodeClientRequest.respondWith (../../node_modules/@mswjs/interceptors/src/interceptors/ClientRequest/NodeClientRequest.ts:555:31)
      at ../../node_modules/@mswjs/interceptors/src/interceptors/ClientRequest/NodeClientRequest.ts:317:14
"msw": "^2.2.0",

BTW, it works fine if I return error

return new HttpResponse(null, {status: 400});

@arkk200
Copy link

arkk200 commented Mar 2, 2024

I have a same issue too...
And it works with fetch instead of axios.
I checked the headers, but in the both case, the headers were same like
{"content-length": "1", "content-type": "application/json"}
I think it's probably a problem with axios library.

@storiesOfRen
Copy link

I'm experiencing a similar issue, where it is not recognizing the axios post method, returning this error, TypeError: Right-hand side of 'instanceof' is not an object
Example post setup it is erroring on: axios.post("/api/reference-url", {id: p.id, searchStr: "some word"})

Version of MSW:
"msw": "^2.2.2",

@renet
Copy link

renet commented Mar 7, 2024

I also ran into axios-related issues with msw. My workaround is to mock axios to use fetch for the requests within my vitest suite. This should also be applicable to Jest (using jest.mock instead of vi.mock). Maybe that helps anyone in the future.

Solution that works for me in a test setup file:

beforeAll(() => {
  // axios needs to be mocked to use fetch in order to work with msw
  vi.mock("axios", () => {
    const isAxiosError = (error: any) => error.isAxiosError === true;
    const handleAxiosResponse = async (response: Response) => {
      if (!response.ok) {
        // eslint-disable-next-line @typescript-eslint/no-throw-literal
        throw {
          isAxiosError: true,
          message: response.statusText,
          response: {
            status: response.status,
            statusText: response.statusText,
            data: null,
            headers: {},
            config: {},
          },
        };
      }

      const data = await response.json();

      return { data };
    };
    const handleError = (error: any) => {
      throw isAxiosError(error)
        ? error
        : {
            isAxiosError: true,
            message: error.message,
          };
    };
    const cleanUpParams = (params?: Record<string, string | undefined>) => {
      return Object.fromEntries(
        Object.entries(params ?? {}).filter(
          (entry): entry is [string, string] => entry[1] !== undefined,
        ),
      );
    };
    const getMethodHandler =
      (method: string) => async (url: string, config?: any) => {
        const queryString = new URLSearchParams(
          cleanUpParams(config?.params),
        ).toString();
        const modifiedUrl = `${url}?${queryString}`;

        return await fetch(modifiedUrl, { ...config, method })
          .then(handleAxiosResponse)
          .catch(handleError);
      };
    const getMethodHandlerWithBody =
      (method: string) => async (url: string, data?: any, config?: any) => {
        const queryString = new URLSearchParams(
          cleanUpParams(config?.params),
        ).toString();
        const modifiedUrl = `${url}?${queryString}`;
        const fetchConfig = {
          ...config,
          method,
          body: data ? JSON.stringify(data) : undefined,
        };

        return await fetch(modifiedUrl, fetchConfig)
          .then(handleAxiosResponse)
          .catch(handleError);
      };

    return {
      default: {
        delete: getMethodHandler("DELETE"),
        get: getMethodHandler("GET"),
        head: getMethodHandler("HEAD"),
        patch: getMethodHandlerWithBody("PATCH"),
        post: getMethodHandlerWithBody("POST"),
        put: getMethodHandlerWithBody("PUT"),
      },
      isAxiosError,
    };
  });
});

@Agent57
Copy link

Agent57 commented Mar 12, 2024

I was stumped by this problem for a while.

I found that fetch was working fine, but axios just kept failing. The MSW server handler was not intercepting the network request and my axios.get() call failed with an 'ENOTFOUND' error as it couldn't resolve the sysaddrinfo() call on my dummy URL.

Eventually though, I did find a very simple solution that worked for me... I switched the import of setupServer from 'msw/native' to 'msw/node' o_0

    "axios": "^1.6.7",
    "react": "18.2.0",
    "react-native": "0.73.5"
    "jest": "^29.6.3",
    "msw": "^2.2.3",

@zibs
Copy link

zibs commented Mar 21, 2024

Just want to also chime in here and say I'm seeing the same issue where the body is undefined/null when using MSW, Axios, and React Native:

"react-native": "0.73.6",
....
"apisauce": "^3.0.1", // uses axios under the hood
"axios": "^1.6.8" // bringing it in just to test with
....
"msw": "^2.2.9",

Fetch works fine! I've followed the basic set up steps in repo. I see similar comments: #1775 (comment) and here: #1926 (comment) (same user)

It sounds like it's somehow related to the XMLHttpRequest interceptor/stack...I do notice that it seems to be hitting the /browser codepath, and not the /node/native codepath (if that matters).

@lcandiago
Copy link

Same problem here. Using MSW with Axios in React Native doesn't work. The request response is not finalized and does not return the JSON as it should.

@zibs
Copy link

zibs commented Mar 22, 2024

Hey @kettanaito

I've set up a basic reproducible example here (as simple as I could): https://github.com/zibs/mvce-msw-rn if you have the chance to take a look. The warning that is thrown in the video is " Cannot retrieve XMLHttpRequest response body as XML: DOMParser is not defined. You are likely using an environment that is not browser or does not polyfill browser globals correctly.", but I don't think is totally relevant (but maybe?)...

I'm happy to continue digging in, but you might be able to diagnose it much faster!

When digging I noticed that here https://github.com/mswjs/interceptors/blob/133754688adeb47cb972ab358db5e77f30084e03/src/interceptors/XMLHttpRequest/XMLHttpRequestController.ts#L335 there is never a .body in the response, but there is a ._bodyInit and ._bodyText... not sure if that's helpful/important.

Let me know if you want a separate issue created or anything.

@zibs
Copy link

zibs commented Mar 27, 2024

Yeah, React Native doesn't natively support a .body on the Response object, so the interceptor will always fail. Trying to think of a solution, open to ideas!

React Native also doesn't natively support ReadableStream out of the box yet either I don't think....

@akmjenkins
Copy link

I've been looking at this over here: #2085 and have narrowed something similar down to a repro.

When you run msw/axios in an environment that uses the node >= 18 APIs for Request/Response, everything works. But when running in an environment that gets those APIs from elsewhere - specifically whatwg-fetch, which is common in react projects in the testing environment, not sure about react-native but I did notice the dependency in @zibs repo - then it's borked.

@akmjenkins
Copy link

akmjenkins commented Mar 29, 2024

This is not an issue with MSW, it's an issue with whatwg-fetch - I think this one - which might make it an issue to be raised over there (although the one I've referenced is closed), or perhaps an issue could be raised in react-native to use a different polyfill.

EDIT: Also same issue in RN repo. Hey, at least that one's open!
EDIT 2: Possible solution SO answer

@batu0b
Copy link
Author

batu0b commented Mar 30, 2024

First of all, thank you all very much for your answers, I could not follow the issues for a while. I used an alternative library as a solution, thank you.

@XantreDev
Copy link

XantreDev commented Apr 10, 2024

Handler:

http.get('*', ({ request }) => {
    return HttpResponse.json({
      data: { bebe: 1 },
    });
  })
  LOG  13:40:52:219 [xhr:GET https://someurl.com] open GET https://someurl.com
 LOG  13:40:52:228 [xhr:GET https://someurl.com] registered event "timeout" function handleTimeout() { [bytecode] }
 LOG  13:40:52:233 [xhr:GET https://someurl.com] addEventListener timeout function handleTimeout() { [bytecode] }
 LOG  13:40:52:242 [xhr:GET https://someurl.com] setRequestHeader Accept application/json, text/plain, */*
 LOG  13:40:52:249 [xhr:GET https://someurl.com] registered event "load" function () { [bytecode] }
 LOG  13:40:52:258 [xhr:GET https://someurl.com] addEventListener load function () { [bytecode] }
 LOG  13:40:52:267 [xhr:GET https://someurl.com] converting request to a Fetch API Request...
 LOG  13:40:52:275 [xhr:GET https://someurl.com] converted request to a Fetch API Request! {"url":"https://someurl.com","credentials":"include","headers":{"map":{"accept":"application/json, text/plain, */*"}},"method":"GET","mode":null,"signal":{},"referrer":null,"bodyUsed":false,"_bodyInit":null,"_noBody":true,"_bodyText":""}
 LOG  13:40:52:284 [xhr:GET https://someurl.com] awaiting mocked response...
 LOG  13:40:52:293 [xhr:GET https://someurl.com] emitting the "request" event for 2 listener(s)...
 LOG  13:40:54:483 [xhr:GET https://someurl.com] all "request" listeners settled!
 LOG  13:40:54:495 [xhr:GET https://someurl.com] event.respondWith called with: {"type":"default","status":200,"ok":true,"statusText":"OK","headers":{"map":{"content-type":"application/json","content-length":"19"}},"url":"","bodyUsed":false,"_bodyInit":"{\"data\":{\"bebe\":1}}","_bodyText":"{\"data\":{\"bebe\":1}}"}
 LOG  13:40:54:503 [xhr:GET https://someurl.com] received mocked response: 200 OK
 LOG  13:40:54:513 [xhr:GET https://someurl.com] responding with a mocked response: 200 OK
 LOG  13:40:54:519 [xhr:GET https://someurl.com] calculated response body length 19
 LOG  13:40:54:528 [xhr:GET https://someurl.com] trigger "loadstart" {"loaded":0,"total":19}
 LOG  13:40:54:535 [xhr:GET https://someurl.com] setReadyState: 1 -> 2
 LOG  13:40:54:542 [xhr:GET https://someurl.com] set readyState to: 2
 LOG  13:40:54:548 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  13:40:54:554 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  13:40:54:560 [xhr:GET https://someurl.com] setReadyState: 2 -> 3
 LOG  13:40:54:566 [xhr:GET https://someurl.com] set readyState to: 3
 LOG  13:40:54:573 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  13:40:54:581 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  13:40:54:593 [xhr:GET https://someurl.com] finalizing the mocked response...
 LOG  13:40:54:603 [xhr:GET https://someurl.com] setReadyState: 3 -> 4
 LOG  13:40:54:609 [xhr:GET https://someurl.com] set readyState to: 4
 LOG  13:40:54:618 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  13:40:54:627 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  13:40:54:634 [xhr:GET https://someurl.com] trigger "load" {"loaded":0,"total":19}
 LOG  13:40:54:643 [xhr:GET https://someurl.com] found 1 listener(s) for "load" event, calling...
 LOG  13:40:54:651 [xhr:GET https://someurl.com] getResponse (responseType: )
 LOG  13:40:54:659 [xhr:GET https://someurl.com] resolving "" response type as text
 LOG  13:40:54:666 [xhr:GET https://someurl.com] getAllResponseHeaders
 LOG  13:40:54:675 [xhr:GET https://someurl.com] resolved all response headers to content-type: application/json
content-length: 19
 LOG  13:40:54:684 [xhr:GET https://someurl.com] emitting the "response" event for 1 listener(s)...
 LOG  13:40:54:698 [xhr:GET https://someurl.com] trigger "loadend" {"loaded":0,"total":19}
 LOG  13:40:54:708 [xhr:GET https://someurl.com] found a direct "loadend" callback, calling...
 LOG  13:40:54:719 [xhr:GET https://someurl.com] getAllResponseHeaders
 LOG  13:40:54:726 [xhr:GET https://someurl.com] resolved all response headers to content-type: application/json
content-length: 19
 LOG  13:40:54:734 [xhr:GET https://someurl.com] getResponseText: "

@XantreDev
Copy link

I've found workaround. We can use _bodyInit if we have no body.

diff --git a/lib/browser/chunk-65PS3XCB.js b/lib/browser/chunk-65PS3XCB.js
index e4f824e7d40d3d2a48c86b2420cbfd8e7c2c53ed..07228344b25fb6b44bb396f4b9eb93332d11cac2 100644
--- a/lib/browser/chunk-65PS3XCB.js
+++ b/lib/browser/chunk-65PS3XCB.js
@@ -455,6 +455,13 @@ var XMLHttpRequestController = class {
         readNextResponseBodyChunk();
       };
       readNextResponseBodyChunk();
+    } else if (response._bodyInit) {
+      this.logger.info('mocked response has _bodyInit, faking streaming...')
+
+      const bodyInit = response._bodyInit
+      const encoder = new TextEncoder()
+      this.responseBuffer = encoder.encode(bodyInit)
+      finalizeResponse()
     } else {
       finalizeResponse();
     }

Here is fixed logs:

 LOG  14:56:48:940 [xhr:GET https://someurl.com] received mocked response: 200 OK
 LOG  14:56:48:964 [xhr:GET https://someurl.com] responding with a mocked response: 200 OK
 LOG  14:56:48:982 [xhr:GET https://someurl.com] calculated response body length 19
 LOG  14:56:49:11 [xhr:GET https://someurl.com] trigger "loadstart" {"loaded":0,"total":19}
 LOG  14:56:49:37 [xhr:GET https://someurl.com] setReadyState: 1 -> 2
 LOG  14:56:49:59 [xhr:GET https://someurl.com] set readyState to: 2
 LOG  14:56:49:99 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  14:56:49:139 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  14:56:49:180 [xhr:GET https://someurl.com] setReadyState: 2 -> 3
 LOG  14:56:49:223 [xhr:GET https://someurl.com] set readyState to: 3
 LOG  14:56:49:262 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  14:56:49:298 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  14:56:49:340 [xhr:GET https://someurl.com] mocked response has _bodyInit, faking streaming...
 LOG  14:56:49:387 [xhr:GET https://someurl.com] finalizing the mocked response...
 LOG  14:56:49:433 [xhr:GET https://someurl.com] setReadyState: 3 -> 4
 LOG  14:56:49:476 [xhr:GET https://someurl.com] set readyState to: 4
 LOG  14:56:49:521 [xhr:GET https://someurl.com] triggerring "readystatechange" event...
 LOG  14:56:49:566 [xhr:GET https://someurl.com] trigger "readystatechange"
 LOG  14:56:49:608 [xhr:GET https://someurl.com] trigger "load" {"loaded":19,"total":19}
 LOG  14:56:49:648 [xhr:GET https://someurl.com] found 1 listener(s) for "load" event, calling...
 LOG  14:56:49:692 [xhr:GET https://someurl.com] getResponse (responseType: )
 LOG  14:56:49:743 [xhr:GET https://someurl.com] resolving "" response type as text {"data":{"bebe":1}}
 LOG  14:56:49:785 [xhr:GET https://someurl.com] getAllResponseHeaders
 LOG  14:56:49:837 [xhr:GET https://someurl.com] resolved all response headers to content-type: application/json
content-length: 19
 LOG  14:56:49:887 [xhr:GET https://someurl.com] emitting the "response" event for 1 listener(s)...
 LOG  14:56:49:933 [xhr:GET https://someurl.com] trigger "loadend" {"loaded":19,"total":19}
 LOG  14:56:49:976 [xhr:GET https://someurl.com] found a direct "loadend" callback, calling...
 LOG  14:56:50:32 [xhr:GET https://someurl.com] getAllResponseHeaders
 LOG  14:56:50:71 [xhr:GET https://someurl.com] resolved all response headers to content-type: application/json
content-length: 19
 LOG  14:56:50:118 [xhr:GET https://someurl.com] getResponseText: "{"data":{"bebe":1}}"

@XantreDev
Copy link

@kettanaito Can this workaround to be useful inside interceptors package?

@hardouinyann
Copy link

Any fix planned ?

@XantreDev
Copy link

@hardouinyann you can use my patch for now

@hardnold
Copy link

I've found workaround. We can use _bodyInit if we have no body.

diff --git a/lib/browser/chunk-65PS3XCB.js b/lib/browser/chunk-65PS3XCB.js
index e4f824e7d40d3d2a48c86b2420cbfd8e7c2c53ed..07228344b25fb6b44bb396f4b9eb93332d11cac2 100644
--- a/lib/browser/chunk-65PS3XCB.js
+++ b/lib/browser/chunk-65PS3XCB.js
@@ -455,6 +455,13 @@ var XMLHttpRequestController = class {
         readNextResponseBodyChunk();
       };
       readNextResponseBodyChunk();
+    } else if (response._bodyInit) {
+      this.logger.info('mocked response has _bodyInit, faking streaming...')
+
+      const bodyInit = response._bodyInit
+      const encoder = new TextEncoder()
+      this.responseBuffer = encoder.encode(bodyInit)
+      finalizeResponse()
     } else {
       finalizeResponse();
     }

For anybody who comes across this, this patch needs to be applied on the @mswjs/interceptors-package, NOT msw

@XantreDev
Copy link

Yep that's true

@prkgnt
Copy link

prkgnt commented May 4, 2024

It's working! I hope it will merge asap! thank you! @XantreDev

@XantreDev
Copy link

XantreDev commented May 4, 2024

It's working! I hope it will merge asap! thank you! @XantreDev

I would like to provide a PR if @kettanaito consider it usefull

@GriffinSauce
Copy link

GriffinSauce commented Jun 13, 2024

@kettanaito is this fix something you'd consider merging? (asking because this issue is closed at the moment)

I'd rather only apply a patch as a stopgap for a proper fix, otherwise MSW just isn't an option for us for now :(

@gfgabrielfranca
Copy link

@kettanaito, any update on this?

@Yamadetta
Copy link

@kettanaito Hello!
The problem is still relevant, the patch above helps to fix it. Will there be a fix according to the patch?

@github-actions github-actions bot locked and limited conversation to collaborators Nov 9, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests