mswjs / msw

Industry standard API mocking for JavaScript.
https://mswjs.io
MIT License
15.93k stars 517 forks source link

Non-deterministic Error: connect ECONNREFUSED #2295

Closed ericyd closed 1 month ago

ericyd commented 1 month ago

Prerequisites

Environment check

Node.js version

v20.17.0

Reproduction repository

https://github.com/ericyd/msw-econnrefused/

Reproduction steps

Unfortunately the error is non-deterministic, but here is how I reproduced

  1. Open repository in Codespaces. This is important because I cannot reproduce the issue on my local machine (MacOS), only in GitHub Actions and GitHub Codespaces. (At time of writing, GitHub offers 120 hours of free Codespaces usage on free accounts so I hope this isn't a blocker for the MSW maintainers)
  2. npm ci (if first time booting the codespace)
  3. npm test
  4. As needed, press Enter in the terminal to re-run the test suite. For me, the tests will error within the first 10 executions which takes less than a minute to achieve.

Current behavior

Here is the full console transcript from an error run:

``` test/TestComponent.spec.ts x6 ✓ test/TestComponent.spec.ts (2 tests) 49ms stderr | warn$1 (/workspaces/msw-econnrefused/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:53:13) [Vue warn]: Unhandled error during execution of mounted hook at at ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Vitest caught 1 unhandled error during the test run. This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected. ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ TypeError: fetch failed ❯ node:internal/deps/undici/undici:13178:13 ❯ processTicksAndRejections node:internal/process/task_queues:95:5 ❯ src/TestComponent.vue:7:3 5| await fetch('http://localhost:8000/account') 6| await fetch('http://localhost:8000/user') 7| await fetch('http://localhost:8000/account') | ^ 8| await fetch('http://localhost:8000/user') 9| }) This error originated in "test/TestComponent.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. { stack: 'AggregateError: \n' + ' at internalConnectMultiple (node:net:1118:18)\n' + ' at afterConnectMultiple (node:net:1685:7)', errors: [ { stack: 'Error: connect ECONNREFUSED ::1:8000\n' + ' at createConnectionError (node:net:1648:14)\n' + ' at afterConnectMultiple (node:net:1678:16)', message: 'connect ECONNREFUSED ::1:8000', errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '::1', port: 8000, constructor: 'Function', name: 'Error', toString: 'Function' }, { stack: 'Error: connect ECONNREFUSED 127.0.0.1:8000\n' + ' at createConnectionError (node:net:1648:14)\n' + ' at afterConnectMultiple (node:net:1678:16)', message: 'connect ECONNREFUSED 127.0.0.1:8000', errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 8000, constructor: 'Function', name: 'Error', toString: 'Function' } ], code: 'ECONNREFUSED', stackStr: 'AggregateError: \n' + ' at internalConnectMultiple (node:net:1118:18)\n' + ' at afterConnectMultiple (node:net:1685:7)', nameStr: 'AggregateError', expected: 'undefined', actual: 'undefined', message: '', constructor: 'Function', name: 'Caused by: AggregateError', toString: 'Function', stacks: [ { method: 'internalConnectMultiple', file: '/workspaces/msw-econnrefused/node:net', line: 1118, column: 18 }, { method: 'afterConnectMultiple', file: '/workspaces/msw-econnrefused/node:net', line: 1685, column: 7 } ] } ❯ internalConnectMultiple node:net:1118:18 ❯ afterConnectMultiple node:net:1685:7 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errors: [ { stack: 'Error: connect ECONNREFUSED ::1:8000\n at createConnectionError (node:net:1648:14)\n at afterConnectMultiple (node:net:1678:16)', message: 'connect ECONNREFUSED ::1:8000', errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '::1', port: 8000, constructor: 'Function', name: 'Error', toString: 'Function' }, { stack: 'Error: connect ECONNREFUSED 127.0.0.1:8000\n at createConnectionError (node:net:1648:14)\n at afterConnectMultiple (node:net:1678:16)', message: 'connect ECONNREFUSED 127.0.0.1:8000', errno: -111, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 8000, constructor: 'Function', name: 'Error', toString: 'Function' } ], code: 'ECONNREFUSED' } ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Test Files 1 passed (1) Tests 2 passed (2) Errors 1 error Start at 20:29:34 Duration 168ms FAIL Tests failed. Watching for file changes... press h to show help, press q to quit @ericyd ➜ /workspaces/msw-econnrefused (main) $ ```

In addition, I recorded a video showing the non-deterministic nature of this error

https://github.com/user-attachments/assets/ab0fa80b-0c56-438f-9875-2d6661d3f3d2

Expected behavior

There should never be a fetch error, or if there is, it should always be deterministic.

Additional notes

  1. Although the tests in this video are passing with errors, I came across this bug while investigating real test failures in our suite at work. I believe both scenarios share the same root cause (non-deterministic behavior).
  2. My report is extremely similar to https://github.com/mswjs/msw/issues/1351 and therefore I tried polyfilling fetch. It did not solve the issue, and in addition I don't think that's the problem since it doesn't always error.
kettanaito commented 1 month ago

Hi, @ericyd. Thanks for reporting this.

This seems to be related to flushPromises issue: https://github.com/mswjs/msw/issues/1163#issuecomment-1068294420. Can you please follow this thread and let me know if the suggestions fix your issue? Basically, you have to flush the promises twice.

ericyd commented 1 month ago

Thanks @kettanaito , that solved the issue! I re-ran the tests using my steps to reproduce and was able to re-run 50x without failure. I consider that resolved. I've committed the fix to the demonstration repo: https://github.com/ericyd/msw-econnrefused/commit/92ef0c901fee82ebcd06a98ffe227b079002ef26