| Name | Version |
| ---- | ------- |
| msw | 0.21.3 |
| node | 12 |
| OS | Linux / OSX |
import { setupServer } from 'msw/node'
import { rest } from 'msw'
const server = setupServer(
rest.post(
'/api/test',
async (req, res, ctx) => res(
ctx.json({ test: 'hello' })
),
),
);
server.listen()
const res = await fetch("/api/test", {
method: "POST",
body: JSON.stringify({}),
});
const obj = await res.json();
expect(obj).toMatchInlineSnapshot(`
Object {
"test": "hello",
}
`);
Using jest.useFakeTimers() the response is not returned by the server provided by msw.
The handler is called correctly, but the response hangs.
Use jest.useFakeTimers() into some tests without affecting msw.
> jest
FAIL src/example.test.js (5.762 s)
SOAP Utils
✕ Test (5012 ms)
● SOAP Utils › Test
: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:
1 | describe("SOAP Utils", () => {
> 2 | test(`Test`, async () => {
| ^
3 | const res = await fetch("/api/test", {
4 | method: "POST",
5 | body: JSON.stringify({}),
at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
at Suite.<anonymous> (src/example.test.js:2:3)
at Object.<anonymous> (src/example.test.js:1:1)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
handlers.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 1 passed, 1 total
Time: 6.267 s
Ran all test suites.
Here You can find a repository to reproduce the issue: https://github.com/bud-mo/msw-example
Hey, @bud-mo. Thanks for reporting this, and especially for the reproduction repository.
I'm debugging the issue and I can see that the response Promise resolves correctly, as we replace native setTimeout with require('timers').setTimeout. However, the test still timeouts. I believe that fake timers are not used properly in the test. Perhaps they need to be advanced for the test to conclude?
I'm researching this at the moment, but I don't have much experience with fake timers, so it's going to take some time. If you are familiar with them, could you double check how you've used them on other projects, or on the web?
However, the whatwg-fetch library you use to polyfill fetch for tests does use native setTimeout to handle XHR events:
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
};
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
var body = 'response' in xhr ? xhr.response : xhr.responseText;
setTimeout(function() {
resolve(new Response(body, options));
}, 0);
};
Since stubbing instances affects the entire NodeJS process, using fake timers with Jest effectively stubs all
setTimeoutfunctions, including those function calls in third-party libraries likewhatwg-fetch.
That may be the reason your test timeouts. That may also be the reason why if you console.log(obj) you will see the mocked response _after_ the test has timed out. To my best knowledge, it looks like the fake timers must be progressed in order for those native setTimeout calls to execute.
I've also found a few threads that mention that you cannot use async/await syntax with fake timers in Jest:
You can find more info of why that's so in the first referenced link.
If you swap whatwg-fetch with another request issuing library you can see the test passing, although still using fake timers and MSW. Here's your test using native http module to make a request (no setTimeout calls):
import http from 'http'
test(`Test`, (done) => {
let resBody = ''
// Resolving against the current location, because relative URLs don't exist in NodeJS.
const req = http.request(new URL('/api/test', location.href), {
method: 'POST',
})
req.on('response', (res) => {
res.on('data', (chunk) => (resBody += chunk))
res.on('end', () => {
const resBodyObject = JSON.parse(resBody)
expect(resBodyObject).toMatchInlineSnapshot(`
Object {
"test": "hello",
}
`)
done()
})
})
req.write(JSON.stringify({}))
req.end()
})
I don't think this is the issue with MSW. I suggest you to:
whatwg-fetch or jest repositories, the problem lies in fake timers + fetch polyfill + (potentially) async/await syntax.whatwg-fetch polyfill with a compatible implementation that doesn't rely on setTimeout.whatwg-fetch timers when using fake timers in Jest.I'm always willing to reopen the issue if we find sufficient proof it's MSW problem.
@kettanaito Thank you for the support!
Please let us know what would be the solution to this. We could include fake timers in some usage examples for others to follow. Thanks!
@kettanaito I ended up using node-fetch.
In my case is easy to use because I am wrapping the fetch to inject some auth headers to each request, so I have mocked that function to use the fetch provided by node-fetch, I only need to add the new URL('/api/test', location.href) line and it works great.
For documentation purpose I think we can provide an example with a simple fetch wrapper around node-fetch.
I created a branch with an updated working example with a possible solution, this patch can be a starting point:
https://github.com/bud-mo/msw-example/commit/b4ba10f1fcbed65b6acf314b8a7a3fda92ee2792