Replies: 3 comments 5 replies
-
|
Hi, @v-mwalk. This behavior is correct. Reading the request/response body is an asynchronous action. Even if it takes 0ms, it will always complete on the next tick of the event loop, giving you this out-of-order behavior.
Note that this isn't what life-cycle API is for. It's meant as the means to listen to request/response execution as a side effect. It's correct that those events are emitted after MSW is finished handling respective request/response instances. Maybe you can tell me more about your requirement? What makes you want this kind of behavior? |
Beta Was this translation helpful? Give feedback.
-
|
I'm not forcing sync on async and yes, when i'm reading the request body
etc then i am awaiting etc. All that works nicely. The issue is the event
firing which in v2.0.0 has gone async - worked well in <2.0.0 where it was
syncronous - when the AUT response was received , the test suite was sure
all activity was over and complete. However, in v2+ that is not the case,
the event may not have fired, happening sometime after.
Lol - probably not explaining it well. I'll try and get some time to knock
up a quick n simple example. It is not a big deal as <v2.0.0 is actually
working well - with iNest AUTs..
…On Mon, 29 Jan 2024, 21:11 Artem Zakharchenko, ***@***.***> wrote:
There's not much we can/should do about this. Reading the request body
stream is async so you must await that in your tests/tooling. Most test
frameworks work nicely with awaiting promises both in the tests and in the
setup hooks so you should utilize those (forcing a synchronous nature of a
test that operates on asynchronous resources like requests and responses
isn't a good idea).
—
Reply to this email directly, view it on GitHub
<#1927 (reply in thread)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADAIJVULDTRYVSUYTKASGD3YQ57W7AVCNFSM6AAAAABATX2DX6VHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DEOBQGY4DS>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
|
@v-mwalk I need to use lifecycle events to track analytics requests and assert on them in tests too. I also do not understand why this was done. If I want an event handler to not wait I could make it a normal function and handle promises inside in async manner. Both options would be valid. But with decision to not await async even handlers I can't decide and I am stuck with doing the patching below or only capturing promises and awaiting in my tests which breaks my abstraction of custom matcher I had a nice mini framework around that which broke here so I had to create a wrapper like this: import {http as originalHttp, type HttpResponseResolver, type PathParams, type DefaultBodyType} from 'msw';
import {FIRED_MOCKED_REQUESTS} from './firedRequests';
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
async function readBody(request: Request): Promise<unknown> {
if (!request.body) {return undefined;}
const contentType = request.headers.get('content-type');
if (contentType?.includes('application/json')) {
return request.json();
} else if (contentType?.includes('application/x-www-form-urlencoded')) {
return Object.fromEntries(new URLSearchParams(await request.text()).entries());
} else {
return request.text();
}
}
function wrapResolver<
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = DefaultBodyType,
>(resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>): HttpResponseResolver<Params, RequestBodyType, ResponseBodyType> {
return async (info) => {
const clonedRequest = info.request.clone();
const response = await resolver(info);
// Don't track passthrough responses (they have x-msw-intention header)
const isPassthrough = response instanceof Response && response.headers.get('x-msw-intention') === 'passthrough';
if (!isPassthrough) {
const body = await readBody(clonedRequest);
FIRED_MOCKED_REQUESTS.push({request: clonedRequest, body});
}
return response;
};
}
function createWrappedMethod(method: HttpMethod) {
return <
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = DefaultBodyType,
>(path: string, resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>, options?: Parameters<typeof originalHttp.get>[2]) => {
return originalHttp[method](path, wrapResolver(resolver), options);
};
}
export const http = {
get: createWrappedMethod('get'),
post: createWrappedMethod('post'),
put: createWrappedMethod('put'),
patch: createWrappedMethod('patch'),
delete: createWrappedMethod('delete'),
head: createWrappedMethod('head'),
options: createWrappedMethod('options'),
all: <
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = DefaultBodyType,
>(path: string, resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>, options?: Parameters<typeof originalHttp.all>[2]) => {
return originalHttp.all(path, wrapResolver(resolver), options);
},
};Previously it was easy like this: export const FIRED_MOCKED_REQUESTS: Array<{request: Request; body: unknown}> = [];
msw.events.on('response:mocked', async ({request}) => {
request = request.clone();
if (!request.body) {
FIRED_MOCKED_REQUESTS.push({request, body: undefined});
} else if (request.headers.get('content-type') === 'application/x-www-form-urlencoded') {
FIRED_MOCKED_REQUESTS.push({
request,
body: Object.fromEntries(new URLSearchParams(await request.text()).entries()),
});
} else if (request.headers.get('content-type') === 'application/json') {
FIRED_MOCKED_REQUESTS.push({request, body: await request.json()});
} else {
FIRED_MOCKED_REQUESTS.push({request, body: await request.text()});
}
});Then I could assert on |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
In the MSW based test framework we have, the lifecycle events request:start/response:bypass/response/mocked are used for logging; basically request/responses are stringified and then written to files for offline use.
In 1.x.x all worked ok. However, in 2.x.x (2.0.11 currently being used) often the test/s end before the logging has completed.
The reason is that the cloned request and response .text() awaits do not return until AFTER the AUT has completed it's request (MSW has made the mocked response or passthough response got). For the logging to work, based on the lifecycle events, I need to be able to get the request/response details (and so the bodies) before MSW completes. Is this possible?
EG. In the following, I dont see the ... and push until after the AUT completes and gets it's mocked response
If comment out the await request.clone().text() (and replace with
"blah") then all is good; everything works well.Beta Was this translation helpful? Give feedback.
All reactions