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

Axios never receives mocked response object msw@0.38.1 #1125

Closed
Nnadozie opened this issue Feb 27, 2022 · 26 comments · Fixed by #1136
Closed

Axios never receives mocked response object msw@0.38.1 #1125

Nnadozie opened this issue Feb 27, 2022 · 26 comments · Fixed by #1136
Assignees
Labels
blocked bug Something isn't working

Comments

@Nnadozie
Copy link

Nnadozie commented Feb 27, 2022

Describe the bug

I have set up a simple interceptor, and can log my mocked response using the lifecycle event 'response:mocked', however my mocked response is never received by my axios get request.

After reading through #180 and trying out msw@0.17.1, the issue is resolved, so my initial thought is that this is likely a regression.
However, when recreating the issue in codesandbox I notice the issue is resolved with msw@0.38.1 and node v14.18.1

Environment

  • msw: 0.38.1
  • nodejs: 17.1.0
  • npm: 8.1.2
  • axios: 0.26.0

Please also provide your browser version.
This is a node environment.

To Reproduce

I have created a very simple test in a similar nestjs environment to the one I use. Codesandbox link

import { rest } from 'msw';
import { setupServer, SetupServerApi } from 'msw/node';
import axios from 'axios';

describe('AppController', () => {
  let server: SetupServerApi;
  beforeAll(async () => {
    server = setupServer(
      rest.get('https://contest.com/', (req, res, ctx) => {
        return res(
          ctx.json({
            message: 'got some data',
          }),
        );
      }),
    );
    server.listen();
  });

  afterAll(async () => {
    server.close();
  });

  describe('reproduce', () => {
    it('should get mocked response', async () => {
      try {
        const res = await axios.get('https://contest.com/');
        console.log(res);
        expect(res.data.message).toBe('got some data');
      } catch (error) {
        console.error(error);
      }
    });
  });
});

Expected behavior

Expect to see response object with a passing test.

Test passes with msw@0.38.1 and node v14.18.1

image

Current behavior

Test fails with msw@0.38.1 and node v17.1.0

image

@Nnadozie Nnadozie added the bug Something isn't working label Feb 27, 2022
@innocenzi
Copy link

innocenzi commented Feb 27, 2022

I'm seeing the same using node v16.13.1 (tested on v16.14.0 too), axios v0.26.0 and msw v0.38.1. Handlers are called but Axios never receives a response.

EDIT - Here is a reproduction: https://github.com/innocenzi/vitest-msw-issue-1125

> vitest --run

 RUN  v0.5.8 D:/Code/playground/vitest-msw

 ❯ src/index.test.ts (1) 5032ms
   × calls axios 5031ms        

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/index.test.ts > calls axios
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files  1 failed (1)
     Tests  1 failed (1)
      Time  6.87s (in thread 5.03s, 136.59%)

 ELIFECYCLE  Test failed. See above for more details.

@Nnadozie
Copy link
Author

Thanks for sharing @innocenzi. Your comment made me look into testing node LTS v16.14.0 with different msw versions, and it seems to work with:

msw@0.36.8
node: v16.14.0
axios@0.26.0

@innocenzi
Copy link

You are right, I can confirm my tests work with msw@0.36.8 👍

@kettanaito
Copy link
Member

kettanaito commented Mar 1, 2022

Hey, @Nnadozie. Thanks for reporting this.

I can confirm that the reproduction repository has tests passing on 0.38.0 and Node.js 14.17.0. This appears to be an issue related to Node.js versions. We've received quite a few recently and those may even be related.

MSW does not officially support any Node.js version higher than v12. It seems that there were some breaking changes in Node.js 16-17 that affected how we intercept requests.

When running the tests on 16.13.0 I can see some exceptions:

  http GET http://localhost/fetch error when cloning response: TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)
    at createHeadersLenient (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/node-fetch/lib/index.js:1021:28)
    at NodeClientRequest.<anonymous> (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/node-fetch/lib/index.js:1498:20)
    at NodeClientRequest.emit (node:events:390:28)
    at NodeClientRequest.emit (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:286:46)
    at NodeClientRequest.respondWith (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:360:14)
    at NodeClientRequest.<anonymous> (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:188:34)
    at step (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:48:23)
    at Object.next (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:29:53)
    at fulfilled (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:20:58) +0ms
  http GET http://localhost/axios error when cloning response: TypeError: Cannot read properties of undefined (reading 'location')
    at RedirectableRequest._processResponse (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/follow-redirects/index.js:341:35)
    at NodeClientRequest.RedirectableRequest._onNativeResponse (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/follow-redirects/index.js:57:10)
    at Object.onceWrapper (node:events:510:26)
    at NodeClientRequest.emit (node:events:390:28)
    at NodeClientRequest.emit (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:286:46)
    at NodeClientRequest.respondWith (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:360:14)
    at NodeClientRequest.<anonymous> (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:188:34)
    at step (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:48:23)
    at Object.next (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:29:53)
    at fulfilled (/Users/kettanaito/Projects/contrib/msw-1125/node_modules/@mswjs/interceptors/lib/interceptors/ClientRequest/NodeClientRequest.js:20:58) +0ms

The same errors happen on 17.1.0.

We should update MSW and its dependencies to support Node.js 16, I think this will solve the issue with version 17 as well.

@teklakct
Copy link

teklakct commented Mar 4, 2022

I found a similar problem with post rest handlers.

Take a look on that:

import axios from 'axios';
import {rest} from 'msw';
import {setupServer} from 'msw/node';

const URL = 'http://mock.pl';
const MOCKED_RESPONSE = {title: 'Mocked response'};

const client = axios.create();
const server = setupServer(rest.post(URL, (req, res, ctx) => res(ctx.json(MOCKED_RESPONSE))));

beforeAll(() => server.listen());
afterAll(() => server.close());

it('works because post data is not defined', async () => {
    const response = await client.post(URL);

    expect(response.data).toEqual(MOCKED_RESPONSE);
});

it('does not work because there is a post data', async () => {
    const response = await client.post(URL, {});

    expect(response.data).toEqual(MOCKED_RESPONSE);
});

Tested with

  • msw v0.38.0+
  • node v14.18.2, v16.13 and v16.14
  • axios v0.26.0

@github-actions
Copy link

github-actions bot commented Mar 7, 2022

🎉 This issue has been resolved in version 0.39.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@sbrandwoo
Copy link

@kettanaito I'm still seeing this issue with MSW 0.39.1.

@teklakct's reproduction above still fails with a timeout.

@kettanaito
Copy link
Member

@sbrandwoo, please create a GitHub repo or Codesandbox where I can look into that. Thanks.

@sbrandwoo
Copy link

@kettanaito Here you go: https://github.com/sbrandwoo/msw-timeout-example

 FAIL  src/example.test.js (5.463 s)
  ✓ works because post data is not defined (20 ms)
  ✕ does not work because there is a post data (5001 ms)

  ● does not work because there is a post data

    thrown: "Exceeded timeout of 5000 ms for a test.
    Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

      18 | });
      19 |
    > 20 | it('does not work because there is a post data', async () => {
         | ^
      21 |   const response = await client.post(URL, {});
      22 |
      23 |   expect(response.data).toEqual(MOCKED_RESPONSE);

      at Object.<anonymous> (src/example.test.js:20:1)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:404:19)

Tests pass with MSW 0.36.8.

@kettanaito
Copy link
Member

Thanks for the repository, @sbrandwoo. I can confirm I can reproduce the issue locally. Will update this thread with more info once I have it.

@kettanaito
Copy link
Member

Insight

The issue manifest from the connection to http://mock.pl hangs up the socket that attempts it:

 Error: socket hang up
    at connResetException (node:internal/errors:691:14)
    at Socket.socketOnEnd (node:_http_client:466:23)
    at Socket.emit (node:events:532:35)
    at endReadableNT (node:internal/streams/readable:1346:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  code: 'ECONNRESET'
}

The interceptors catch this error but cannot replay it because the NodeClientRequest.end method is not called at all:

2022-03-10T14:42:42.509Z http normalizeWriteArgs normalizing ClientRequest.write arguments... [ <Buffer 7b 7d>, undefined, [Function (anonymous)] ]
2022-03-10T14:42:42.509Z http normalizeWriteArgs successfully normalized ClientRequest.write arguments: [ <Buffer 7b 7d>, undefined, [Function (anonymous)] ]
2022-03-10T14:42:42.509Z http POST http://mock.pl/ write: {
  chunk: <Buffer 7b 7d>,
  encoding: undefined,
  callback: [Function (anonymous)]
}
2022-03-10T14:42:42.509Z http POST http://mock.pl/ chunk successfully stored! [ <Buffer 7b 7d> ]
2022-03-10T14:42:42.510Z http POST http://mock.pl/ event:socket

# SHOULD BE CALLED HERE

2022-03-10T14:42:47.507Z http override done, restoring modules...

Looks like ClientRequest in Node.js doesn't call the .end() method if there's a socket error. I wonder why this isn't the case in our integration tests where we also connect to non-existing hostnames and get the connection errors but still observe the .end() being called. This may be related to the nature of the error.

@kettanaito
Copy link
Member

More insight

Creating requests with axios establishes the following dependency tree:

axios
  - follow-redirects
    - RedirectableRequest (wrapper)
      - http/https . request

Axios calls the req.end(), where req is instance of RedirectableRequest. The RedirectableRequest, however, calls the .end() on the original http.request (ClientRequest) instance in the .write() callback:

    this.write(data, encoding, function shouldEndAfter() {
      console.log('write callback: calling currentRequest.end()...')
      self._ended = true;
      currentRequest.end(null, null, callback);
    });

Now the root cause of the issue: NodeClientRequest only collects body chunks upon .write() calls. It then does the actual writing (and callback execution) as a part of the .end() method:

https://github.com/mswjs/interceptors/blob/219f9f3cd82f5baf3297f7c6095caa356c148d31/src/interceptors/ClientRequest/NodeClientRequest.ts#L170-L179

And since RedirectableRequest executes .end in the callback of .write, the NodeClientRequest.end never gets called at all. This causes the request instance to pend, reaching the test's timeout.

@chad-vanet
Copy link

Hi @kettanaito, thank you for looking into this issue. It is currently a blocker for my project, would you know a workaround to get the interceptors returning the mocks ?

@mateustalles
Copy link

Also waiting for a possible workaround. Meanwhile, going back to 0.36.8?

@Norfeldt
Copy link

Also waiting for a possible workaround. Meanwhile, going back to 0.36.8?

Going from ^0.39.2 back to 0.36.8 finally got MSW to work with POST request - was working fine with GET request but POST just was not intercepted or ever ended.

@Mr-xzq
Copy link

Mr-xzq commented Mar 30, 2022

@kettanaito Here you go: https://github.com/sbrandwoo/msw-timeout-example

 FAIL  src/example.test.js (5.463 s)
  ✓ works because post data is not defined (20 ms)
  ✕ does not work because there is a post data (5001 ms)

  ● does not work because there is a post data

    thrown: "Exceeded timeout of 5000 ms for a test.
    Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."

      18 | });
      19 |
    > 20 | it('does not work because there is a post data', async () => {
         | ^
      21 |   const response = await client.post(URL, {});
      22 |
      23 |   expect(response.data).toEqual(MOCKED_RESPONSE);

      at Object.<anonymous> (src/example.test.js:20:1)
      at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
      at runJest (node_modules/@jest/core/build/runJest.js:404:19)

Tests pass with MSW 0.36.8.

Is there any other solution to the same problem besides downgrading to 0.36.8 ?

@kettanaito
Copy link
Member

Here's my proposal on how to resolve this issue: follow-redirects/follow-redirects#195. You can help make it happen with your contributions. I don't see a way to address it on MSW's side (see technical insights above).

@kettanaito kettanaito changed the title Axios never receives mocked response object msw@0.38.1 and nodev17.1.0 Axios never receives mocked response object msw@0.38.1 Apr 20, 2022
@kettanaito
Copy link
Member

kettanaito commented Apr 20, 2022

I've opened a pull request in the follow-redirects repository to address this behavior change (follow-redirects/follow-redirects#197). I hope the maintainers will be okay with it and we can merge and propagate it to all the immediate dependencies in the nearest future.

I can also confirm that the proposed change fixes the issue!

@kettanaito
Copy link
Member

I'm so thankful to everybody for providing reproduction repositories and sandboxes! That made debugging and verification of this issue such a breeze.

@kettanaito
Copy link
Member

kettanaito commented Jun 9, 2022

@95th has done some outstanding work to fix this in mswjs/interceptors#259. We've merged the fixed, it should propagate to MSW in the next dependency bump.

So, in the end, no changes on the follow-redirects side were necessary. I still find it somewhat odd the way they call .end() within the .write() callback. It's not prohibited per se but unusual from the usage examples of Node requests I've seen. We've circumvented this by rewriting the way Interceptors will call write callbacks.

@kettanaito kettanaito assigned kettanaito and unassigned kettanaito Jun 9, 2022
@kettanaito
Copy link
Member

The fix should propagate automatically in @mswjs/interceptors@0.16.5. The package will be released tonight. No updates on the MSW side should be necessary, just npm install dependencies again.

@ashish-g-lahane
Copy link

I still face the issue. Just this week I wrote the unit tests from scratch. And facing this issue. I'm mocking HTTP POST btw.

Environment
"msw": "^0.48.3",
Node.js v16.18.0
yarn install v1.22.19
"axios": "^1.1.3"

@ashish-g-lahane
Copy link

please disregard my post earlier. I was doing something wrong in my code, due to which, the interceptors were not being registered for the tests. Its resolved now. My bad. Apologies for the bother caused :(

@kettanaito
Copy link
Member

@ashish-g-lahane, could you please ensure you're using the latest version of MSW? If that still occurs, please create a reproduction repository and share it.

@alebrozzo
Copy link

Hello, I wanted to mention we started getting this error with the latest update of axios.
We are using axios v1.4, react-query v4.32.6, msw (for tests) v1.2.5, and node v16.15.0. Our renovate bot has created a new merge request to update axios to v1.5 and a few of our tests fail. Looking into them the problem is when mocking a call to a post request that has a data parameter, similar to what @teklakct mentioned in this example. If I remove the data parameter from the real call, then the tests pass.

I updated msw to v1.3 but the same thing happens.

@bigcakes
Copy link

@alebrozzo did you ever find a workaround? We are hitting the same issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.