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
Jest tests hang when mocking a stream response #1952
Comments
Note: |
Another note: I tried switching to Axios instead of Supertest, and the problem still remains, so it's not likely an issue with Superagent/Supertest |
I am also experiencing a similar issue. The version of Node.js I am using is v18.19.0. The test code is as follows: /**
* @jest-environment node
*/
import { generateCatMessage } from '@/api/client/generateCatMessage';
import { TooManyRequestsError } from '@/api/errors';
import {
isGenerateCatMessageResponse,
type GenerateCatMessageResponse,
} from '@/features';
import { createInternalApiUrl } from '@/features/url';
import {
mockGenerateCatMessage,
mockGenerateCatMessageTooManyRequestsErrorResponseBody,
} from '@/mocks';
import { afterAll, beforeAll, afterEach, describe, expect, it } from '@jest/globals';
import { http } from 'msw';
import { setupServer } from 'msw/node';
const mockHandlers = [
http.post(createInternalApiUrl('generateCatMessage'), mockGenerateCatMessage),
];
const mockServer = setupServer(...mockHandlers);
const extractResponseBody = (
response: Response,
): ReadableStream<Uint8Array> => {
if (response.body === null) {
throw new Error('generatedResponse.body is null');
}
return response.body;
};
// eslint-disable-next-line
describe('src/api/client/generateCatMessage.ts generateCatMessage TestCases', () => {
beforeAll(() => {
mockServer.listen();
});
afterEach(() => {
mockServer.resetHandlers();
});
afterAll(() => {
mockServer.close();
});
it('should be able to generated CatMessage', async () => {
const generatedResponse = await generateCatMessage({
catId: 'moko',
userId: 'userId1234567890',
message: 'こんにちは!',
});
expect(generatedResponse.body).toBeInstanceOf(ReadableStream);
const generatedResponseBody: ReadableStream<Uint8Array> =
extractResponseBody(generatedResponse);
const expected = [
{
conversationId: '7fe730ac-5ea9-d01d-0629-568b21f72982',
message: 'こんにちは🐱',
},
{
conversationId: '7fe730ac-5ea9-d01d-0629-568b21f72982',
message: 'もこだにゃん🐱',
},
{
conversationId: '7fe730ac-5ea9-d01d-0629-568b21f72982',
message: 'お話しようにゃん🐱',
},
{
conversationId: '7fe730ac-5ea9-d01d-0629-568b21f72982',
message: '🐱🐱🐱',
},
];
const reader = generatedResponseBody.getReader();
const decoder = new TextDecoder();
let index = 0;
const readStream = async (): Promise<undefined> => {
const { done, value } = await reader.read();
if (done) {
return;
}
const objects = decoder
.decode(value)
.split('\n\n')
.map((line) => {
const jsonString = line.trim().split('data: ')[1];
try {
const parsedJson = JSON.parse(jsonString) as unknown;
return isGenerateCatMessageResponse(parsedJson) ? parsedJson : null;
} catch {
return null;
}
})
.filter(Boolean) as GenerateCatMessageResponse[];
for (const object of objects) {
expect(object).toStrictEqual(expected[index]);
index++;
}
await readStream();
};
await readStream();
reader.releaseLock();
}, 10000);
it('should TooManyRequestsError Throw, because unexpected response body', async () => {
mockServer.use(
http.post(
createInternalApiUrl('generateCatMessage'),
mockGenerateCatMessageTooManyRequestsErrorResponseBody,
),
);
const dto = {
catId: 'moko',
userId: 'userId1234567890',
message: 'ねこ!',
} as const;
await expect(generateCatMessage(dto)).rejects.toThrow(TooManyRequestsError);
});
});
The following warning message is being displayed:
Even when I run Jest with the --detectOpenHandles option, the warning is not displayed. Mocks are defined as follows: import { sleep } from '@/utils';
import { HttpResponse, type ResponseResolver } from 'msw';
const encoder = new TextEncoder();
export const mockGenerateCatMessage: ResponseResolver = () => {
const stream = new ReadableStream({
start: async (controller) => {
await sleep();
controller.enqueue(
encoder.encode(
'data: {"conversationId": "7fe730ac-5ea9-d01d-0629-568b21f72982", "message": "こんにちは🐱"}',
),
);
await sleep(0.5);
controller.enqueue(
encoder.encode(
'data: {"conversationId": "7fe730ac-5ea9-d01d-0629-568b21f72982", "message": "もこだにゃん🐱"}',
),
);
await sleep(0.5);
controller.enqueue(
encoder.encode(
'data: {"conversationId": "7fe730ac-5ea9-d01d-0629-568b21f72982", "message": "お話しようにゃん🐱"}',
),
);
await sleep(0.5);
controller.enqueue(
encoder.encode(
'data: {"conversationId": "7fe730ac-5ea9-d01d-0629-568b21f72982", "message": "🐱🐱🐱"}',
),
);
controller.close();
},
});
return new HttpResponse(stream, {
headers: {
'Content-Type': 'text/event-stream',
},
});
}; Additional information: These test codes are being used in our project. You can check the versions of msw and jest by looking at the project's package.json. |
@keitakn, thanks for providing a reproduction repository. In your project, you import You should remove this. All sorts of issues can occur if you are running non-standard
If you're having issues with Jest in modern Node.js, follow the suggestions from the Migration guidelines to resolve them.
ConclusionI understand your frustration with things not working as you expect. MSW by itself doesn't do anything with streams. It doesn't do anything with fetch, requests, or responses. All those are standard APIs used by you and your test/development environment. It so happens that some tools are rather archaic and rely on polyfills for things that have been standard and shipping in both browser and Node.js for years. Those tools bring you down. Migrate from those tools, please. |
I think you may have closed this prematurely. I'm the OP, and my reproduction has nothing to do with "dependencies": {
"jest": "^29.7.0",
"msw": "^2.0.12",
"supertest": "^6.3.3"
} |
@jrnail23, you use Jest. I also think you use JSDOM, otherwise you wouldn't have had this issue. JSDOM does two things that are rather bad for you:
There are all sorts of things that can go wrong in this scenario. I made a decision not to support tools that don't rely on Node.js/JavaScript and instead prefer polluting your tests with polyfills. MSW doesn't own your |
I've tried with and without JSDOM. |
@jrnail23, I highly recommend you see our Usage examples that feature MSW with Jest and Jest+JSDOM and see how they differ from your setup. Let me know what you find! |
will do, thanks for the tip |
@kettanaito I tried emulating what you've got in the jest examples, and the result is unchanged -- still hangs upon test run completion when the stream test runs. |
I recently migrated from Jest to Vitest and it now works without any issues.🙌 I had been interested in Vitest for a while but was hesitant about the migration cost. Your comment was the catalyst for my decision, and I'm glad that I made the switch. msw has been very useful for my project. Thank you for developing msw.🙏 I plan to continue using it in my projects. |
@keitakn, I'm glad to hear migrating to modern tooling helped. |
Prerequisites
Environment check
msw
versionNode.js version
v18.19.0
Reproduction repository
https://github.com/jrnail23/msw-hang-repro
Reproduction steps
npm install
thennpm test
Current behavior
When
repro.test.js
is run, Test passes, but Jest never exits. When onlynormal.test.js
is run (npm test -- --testPathPattern=normal
), test passes, and Jest exits cleanly.Expected behavior
Jest should exit cleanly when test run includes
repro.test.js
The text was updated successfully, but these errors were encountered: