mswjs / msw

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

`TypeError: Body is unusable` on request inspection using Life-cycle events #2319

Closed m-tartari closed 1 month ago

m-tartari commented 1 month ago

Prerequisites

Environment check

Node.js version

v20.12.1

Reproduction repository

Reproduction steps

npm ci npm run test

Current behavior

I'm migrating a large code-base from MSW v1.3.4 to v2.4.11. For some test I'm required to ensure the correctness of the request payload. When I try to inspect the content of the payload for with async request handlers I get a TypeError: Body is unusable

npm run test output ``` ➜ workspace git:(main) ✗ npm run test > msw_test@v1.0.0 test > vitest RUN v2.1.2 /project/workspace stderr | src/components/DeployButton.test.tsx > The Deploy Button > async Error parsing request: TypeError: Body is unusable at specConsumeBody (node:internal/deps/undici/undici:5549:15) at Request.json (node:internal/deps/undici/undici:5451:18) at _Emitter. (/project/workspace/src/utils/testUtils.ts:29:44) at file:///project/workspace/node_modules/strict-event-emitter/src/Emitter.ts:134:16 at Array.forEach () at _Emitter.emit (file:///project/workspace/node_modules/strict-event-emitter/src/Emitter.ts:133:15) at _Emitter.sourceEmit2 [as emit] (file:///project/workspace/node_modules/msw/src/core/utils/internal/pipeEvents.ts:18:19) at handleRequest (file:///project/workspace/node_modules/msw/src/core/utils/handleRequest.ts:116:11) at processTicksAndRejections (node:internal/process/task_queues:95:5) at _Emitter. (file:///project/workspace/node_modules/msw/src/node/SetupServerCommonApi.ts:58:26) Error parsing request: TypeError: Body is unusable at specConsumeBody (node:internal/deps/undici/undici:5549:15) at Request.json (node:internal/deps/undici/undici:5451:18) at _Emitter. (/project/workspace/src/utils/testUtils.ts:29:44) at file:///project/workspace/node_modules/strict-event-emitter/src/Emitter.ts:134:16 at Array.forEach () at _Emitter.emit (file:///project/workspace/node_modules/strict-event-emitter/src/Emitter.ts:133:15) at _Emitter.sourceEmit2 [as emit] (file:///project/workspace/node_modules/msw/src/core/utils/internal/pipeEvents.ts:18:19) at handleRequest (file:///project/workspace/node_modules/msw/src/core/utils/handleRequest.ts:116:11) at processTicksAndRejections (node:internal/process/task_queues:95:5) at _Emitter. (file:///project/workspace/node_modules/msw/src/node/SetupServerCommonApi.ts:58:26) ❯ src/components/DeployButton.test.tsx (2) ❯ The Deploy Button (2) ❯ src/components/DeployButton.test.tsx (2) ❯ The Deploy Button (2) ✓ synch × async ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ FAIL src/components/DeployButton.test.tsx > The Deploy Button > async TypeError: Body is unusable ❯ _Emitter. src/utils/testUtils.ts:29:44 27| if (requestId === id) { 28| try { 29| const payload: T = await request.json() | ^ 30| resolve(isEqual(expectedPayload, payload)) 31| } catch (error) { ❯ node_modules/strict-event-emitter/src/Emitter.ts:134:16 ❯ _Emitter.emit node_modules/strict-event-emitter/src/Emitter.ts:133:15 ❯ _Emitter.sourceEmit2 [as emit] node_modules/msw/src/core/utils/internal/pipeEvents.ts:18:19 ❯ handleRequest node_modules/msw/src/core/utils/handleRequest.ts:116:11 ❯ _Emitter. node_modules/msw/src/node/SetupServerCommonApi.ts:58:26 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ 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: Body is unusable ❯ specConsumeBody node:internal/deps/undici/undici:5549:15 ❯ Request.json node:internal/deps/undici/undici:5451:18 ❯ _Emitter. src/utils/testUtils.ts:29:44 27| if (requestId === id) { 28| try { 29| const payload: T = await request.json() | ^ 30| resolve(isEqual(expectedPayload, payload)) 31| } catch (error) { ❯ node_modules/strict-event-emitter/src/Emitter.ts:134:16 ❯ _Emitter.emit node_modules/strict-event-emitter/src/Emitter.ts:133:15 ❯ _Emitter.sourceEmit2 [as emit] node_modules/msw/src/core/utils/internal/pipeEvents.ts:18:19 ❯ handleRequest node_modules/msw/src/core/utils/handleRequest.ts:116:11 ❯ processTicksAndRejections node:internal/process/task_queues:95:5 ❯ _Emitter. node_modules/msw/src/node/SetupServerCommonApi.ts:58:26 This error originated in "src/components/DeployButton.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "async". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown. ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Test Files 1 failed (1) Tests 1 failed | 1 passed (2) Errors 1 error Start at 14:02:53 Duration 1.60s (transform 91ms, setup 323ms, collect 213ms, tests 115ms, environment 673ms, prepare 102ms) ```

Currently we are using:

Expected behavior

Both test succeed.

kettanaito commented 1 month ago

Hi, @m-tartari. Thanks for reporting this.

Are you per chance forgetting to push the mocks directory? Neither of the reproduction repos have it, and there's only one test (for the button).

Please include the reproduction steps or update those repos. Thanks.

m-tartari commented 1 month ago

Hi @kettanaito The repo is as intended. It contains the smallest codebase within which i could show the issue. The mock handlers are written directly inside tests (src/components/DeployButton.test.tsx#L30-L32. and L48-L54).

It's simple a button that, once clicked, sends a couple of custom props to a back-end with a rest api call. It has a suite of 2 identical tests (they only differ by having sync or async mock handler). Currently one is passing and one failing with a TypeError: Body is unusable when accessing the request payload using Life-Cycle events (in src/utils/testUtils.ts#L29). I would expect the same behavior for both.

For reproduction npm ci && npm run test will trigger the error.

mattcosta7 commented 1 month ago

Hi @kettanaito The repo is as intended. It contains the smallest codebase within which i could show the issue. The mock handlers are written directly inside tests (src/components/DeployButton.test.tsx#L30-L32. and L48-L54.

It's simple a button that, once clicked, sends a couple of custom props to a back-end with a rest api call. It has a suite of 2 identical tests (they only differ by having sync or async mock handler). Currently one is passing and one failing with a TypeError: Body is unusable when accessing the request payload using Life-Cycle events (in src/utils/testUtils.ts#L29). I would expect the same behavior for both.

For reproduction npm ci && npm run test will trigger the error.

I think the issue here is that the same Request instance is here:

https://github.com/m-tartari/msw_lifecicle_body_unusable/blob/45318f0470441ee021ac1440a8d0295d1bc508d5/src/components/DeployButton.test.tsx#L49

and

https://github.com/m-tartari/msw_lifecicle_body_unusable/blob/45318f0470441ee021ac1440a8d0295d1bc508d5/src/utils/testUtils.ts#L29

a Request.prototype.json() can only be called on an instance once. You can request.clone().json() instead in both places to allow it to be read in each.

You could check the payload in the resolver - https://github.com/m-tartari/msw_lifecicle_body_unusable/blob/45318f0470441ee021ac1440a8d0295d1bc508d5/src/components/DeployButton.test.tsx#L49-L53 instead of doing this in the event infrastructure

m-tartari commented 1 month ago

Thank you very much for the explanation and fix. It solved the issue in both the test code and main project!