Node 18+: make `nock` work native `fetch`
johnb8005 opened this issue · comments
Please avoid duplicates
- I checked all open feature requests and none of them matched my request.
Context
Since node v18, fetch
is now natively available and one does not require to install node-fetch
or the like anymore. I have been using the native fetch quite successfully however I can't use it with nock
Alternatives
No response
If the feature request is accepted, would you be willing to submit a PR?
- yes
TBH neither of those directly mention Nock. Would it be possible to add fetch
, node18+
, and undici
as labels?
I Think it might help prevent the need to mark things as duplicate.
If upgrading nock
to work with native fetch
requires some time then at least add something about that to README.
Common issues
mentions axios
for example and it should mention fetch
as its popularity will probably only grow.
Do we have any updates to this? Would love to keep using nock but this is breaking our flow now.
If upgrading
nock
to work with nativefetch
requires some time then at least add something about that to README.
That's a good idea. Thoughts @mastermatt? If someone could get a pull request started that would be great.
It should absolutely be documented in the Readme.
For now we can recommend to set the --no-experiemental-fetch
flag to make it work again.
For what it's worth, my net interceptor library (https://github.com/gr2m/node-net-interceptor) does intercept native fetch as it intercepts at the net
/tls
level (while nock is intercepting at the http
module level which fetch does not use), so there is hope.
If anyone would like to dig into https://github.com/gr2m/node-net-interceptor and native fetch
and see how to successfully intercept a simple GET request, that would be a great start.
Here is a starting point. Intercepting works, but I haven't digged into how the response needs to look like for it to successfully mock a full request lifecycle for the native fetch
import netInterceptor from "@gr2m/net-interceptor";
netInterceptor.start();
netInterceptor.on("connect", (socket, options, bypass) => {
console.log("intercepted!");
// bypass();
});
netInterceptor.on("connection", (socket) => {
socket.write(
`HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Accept-Ranges: bytes
Connection: keep-alive
works`
);
socket.end();
});
const response = await fetch("http://example.com");
console.log(await response.text());
Update: see working example below.
For now we can recommend to set the --no-experiemental-fetch flag to make it work again.
This could only help if one is not using native fetch by desire. Disabling native fetch just makes my code not working at all :)
good point. I mostly work in projects where node-fetch
is used which uses the native fetch
when available and falls back to a custom implementation.
I know it's not ideal, but as a temporary workaround, could you for testing use node-fetch and make it the global fetch
method as described here?
https://github.com/node-fetch/node-fetch#providing-global-access
The best solution I found so far is the msw
library. It works with any HTTP library, including the native fetch
. Here's what I came up with:
import { SetupServer, setupServer } from 'msw/node';
import { rest } from 'msw';
import { myApiClient } from './my-api-client';
type ServerOptions = Parameters<typeof setupServer>;
const withRequestInterception =
(handlers: ServerOptions, test: (server: SetupServer) => any) => async () => {
const server = setupServer(...handlers);
server.listen();
return Promise.resolve(test(server)).finally(() => {
server.resetHandlers();
server.close();
});
};
describe('myApiClient', () => {
it(
'should work!',
withRequestInterception(
[
rest.get('https://my-mocked.url', (req, res, ctx) =>
res(ctx.status(200, 'Mocked status'))
),
],
async () => {
const response = await myApiClient('https://my-mocked.url');
expect(response.status).toEqual(200);
expect(response.statusText).toEqual('Mocked status');
}
)
);
});
Please state that nock does not work with fetch right at the top of your readme.md. Even the most basic nock example fails with the most common way of calling sites - fetch. I've wasted an hour before I realised this expected feature doesn't work:
async function fetchExample() {
nock("http://example.com").get("/").reply(200, "Mocked response");
try {
const response = await fetch("http://example.com");
const data = await response.text();
console.log(data);
} catch (error) {
console.error("An error occurred during the fetch request:", error);
}
}
fetchExample();
I added a warning in dd15ba5
Please state that nock does not work with fetch right at the top of your readme.md. Even the most basic nock example fails with the most common way of calling sites - fetch. I've wasted an hour before I realised this expected feature doesn't work:
async function fetchExample() { nock("http://example.com").get("/").reply(200, "Mocked response"); try { const response = await fetch("http://example.com"); const data = await response.text(); console.log(data); } catch (error) { console.error("An error occurred during the fetch request:", error); } } fetchExample();
+1
@gr2m, are there any plans to support this in near feature? Nock is a great testing library...
I'm not able to work on it by myself right now, I just don't have the time. See my comment at #2397 (comment). I'd be happy to onboard a new co-maintainer if someone wants to take this on
I'm not able to work on it by myself right now, I just don't have the time. See my comment at #2397 (comment). I'd be happy to onboard a new co-maintainer if someone wants to take this on
OK. Let me at least try... How can we do the onboarding?
Please do the work first, I'll make sure you are not blocked. If it works out and you'd like to help maintain the project moving forward, I'd be happy to onboard you
Expanding on the example from #2397 (comment). This results in a successfully mocked response:
import netInterceptor from "@gr2m/net-interceptor";
netInterceptor.start();
netInterceptor.on("connect", (socket, options, bypass) => {
console.log("intercepted!");
// bypass();
});
netInterceptor.on("connection", (socket) => {
const data = "hello world";
const contentLength = Buffer.byteLength(data);
socket.write(
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html; charset=UTF-8\r\n" +
"Content-Encoding: UTF-8\r\n" +
"Accept-Ranges: bytes\r\n" +
"Connection: keep-alive\r\n" +
"Content-Length: " +
contentLength +
"\r\n\r\n" +
data
);
socket.end();
});
const response = await fetch("http://example.com");
console.log(await response.text());
confirmed, thanks a lot! The \r\n
seems to make the difference 👍🏼
For people looking for a way to pass --no-experimental-fetch
to mocha and setup a polyfill, you can see @nikitaeverywhere's snippet found here.
package.json
{
"scripts": {
"test": "mocha --node-option no-experimental-fetch -r tests/_fetch-polyfill.ts tests/**/*.test.ts ...",
},
...
}
tests/_fetch-polyfill.ts
// nock doesn't support native fetch, and hence we need this polyfill.
import fetch, { Headers, Request, Response } from 'node-fetch';
if (!globalThis.fetch) {
(globalThis as any).fetch = fetch;
(globalThis as any).Headers = Headers;
(globalThis as any).Request = Request;
(globalThis as any).Response = Response;
}
That works as a temporary workaround.
After trying polly, nock, @gr2m/net-interceptor and friends, etc, seems like https://github.com/mswjs/interceptors works reliably for native fetch and node-http.
@gr2m maybe can we use mswjs interceptors
as the mocking mechanism and just expose the delightful Nock's API and behavior on top of it? Do you think it will reduce the required effort?
That's a good idea, but it will be quite a big effort, at least if we want to transition nock itself to it. It might be more straight forward to start a separate project build on MSW's interceptors with a nock-compatible API. But then we introduce yet another mocking library 🤷🏼
I started the big effort of decomposing nock that would make this possible, that's why I created this low-level mocking and recording libraries in the first place: #2252. My efforts stagnated because of multiple personal impacts, but I do hope to pick them up one day and conclude them. I sure would love some help if anyone is interested. I even streamed most of my coding for that effort: https://github.com/gr2m/helpdesk#past-shows
So if you or anyone is interested, I'm happy to setup a call and chat about it. It is a big effort, but I also think it has tremendous value to transition a widely used project such as nock
to a modern, maintainable architecture with as little friction to its users as possible.
@gr2m I came up with a more practical solution that lets us do things more gradually. I'm trying to implement this now.
At first, we need to add the missing fetch
support via msw/interceptors
. I THINK it should be surprisingly easy.
About the decomposition PR. IMHO, msw
is probably doing well already, so we don't need to reinvent the wheel. What I like so much about Nock is its stack-like behavior, the fact you can easily insert new nocks inside the tests, and the API.
sounds good, looking forward to see what you come up with 👍🏼
Hey, folks. Got this discussion referenced by @mikicho, and wanted to offer my assistance if you need anything when it comes to supporting fetch in Node, regardless if you decide to use Interceptors or not. I'm certain my knowledge will be useful in bringing the Fetch API support to Nock!
My initial thought was that you can bring only the FetchInterceptor
from @mswjs/interceptors
and use it as the source of requests in Nock. It won't conflict with your request interception algorithm, which is http.ClientRequest
-based (fetch in Node doesn't even use ClientRequest
). I'm afraid I don't know much about the inner structure of Nock to estimate how major of a rework that'd be, but I imagine there's a "given a request, lookup the mock and produce a response" logic where the FetchInterceptor
would fit nicely.
Thanks @kettanaito!
My initial thought was that you can...
This was mine, too. Unfortunately, it seems like the mocking parts and the "nock-related" features code are jumbled in hard-to-separate code. @gr2m started a work to unravel this.
My current path, and I'm not sure if it will work, is to convert the Fetch request (from msw) to ClientRequest.
WDYT?
My current path, and I'm not sure if it will work, is to convert the Fetch request (from msw) to ClientRequest.
You mean from @mswjs/interceptors
, not msw
, right?
I think you should be able to represent Request
as http.ClientRequest
as a temporary measure. From the top of my head, most Fetch features can be represented in ClientRequest
:
- Request's body stream as regular
WritableStream
. I thinknode:stream
may even have a utility to convert Fetch APIReadableStream
into a regular Node stream to then pipe into theClientRequest
. - Request's
Headers
asOutgoingHeaders
. You can use transformer functions from the headers-polyfill to transform one into another. - Request's
signal
andAbortController
via the nativeoptions.signal
on thehttp.ClientRequest
options object.
My concern here is that, as far as I recall, Nock doesn't only lookup the right mock for the request but will also tap into the ClientRequest
to respond to it. Since the ClientRequest
instance in this scenario is just a compatibility detail, responding to it won't do anything. This is where you may meet the most friction since the Interceptors expects a Fetch API Response
instance to be sent from the "request" event listener to respond to an intercepted request.
I may be wrong on this, but it looks like you'd have to use the Request -> ClientRequest -> Response
transformation, which is technically still possible but sounds inefficient.
interceptor.on('request', async ({ request }) => {
// Convert the Request instance to http.ClientRequest.
const clientRequest = toClientRequest(request)
// Pass it to Nock to find a mock and "respond" to
// the intermediary ClientRequest instance.
getResponseFromNock(clientRequest)
// Create a Fetch API Response that Interceptors expect.
const response = new Response()
// Here would be a good place to check somehow if Nock
// has found any mocks and return early if it didn't.
// Convert the response from Nock to the Fetch API response.
clientRequest.on('response', (clientResponse) => {
// write to the "response"...
clientResponse.on('end', () => {
request.respondWith(response)
})
})
})
We actually have a utility that does
IncomingMessage -> Response
transformation in Interceptors. We can expose it as a part of our public API and you can utilize it here instead of implementing it by yourself.
My concern here may be irrelevant depending on how exactly Nock applies the mock response definition onto the intercepted request (maybe there's an abstract representation of that response that you can get and convert it to a Fetch API Response
directly instead of hooking into the ClientRequest
's response
event).
@kettanaito
Thanks for the insights and details!
I ended up doing something very similar, and it's working (woohoo).
#2517
WDYT?
PS: for some reason, got
gets stuck, but fetch
and axios
don't. Would you happen to have an idea of what can cause this?
I let some initial thoughts on the PR but overall looks like a great start. We will get to the got
issue, I've posted some suggestions to try in the Interceptors repo.
FWIW I found that the following code was able to reproduce Nock record
and replay
features with Undici:
'use strict';
const {Agent, MockAgent, setGlobalDispatcher} = require(`undici`);
const fs = require(`node:fs`);
const path = require(`node:path`);
const v8 = require(`node:v8`);
const getNockFile = () => {
const nockFolder = path.join(__dirname, `nock`);
fs.mkdirSync(nockFolder, {recursive: true});
return path.join(nockFolder, `${process.env.NOCK_FILE_NAME}-${process.env.RUN_CLI_ID}.dat`);
};
switch (process.env.NOCK_ENV) {
case `record`:{
const nockFile = getNockFile();
const requests = Object.create(null);
const agent = new Agent({
interceptors: {
Agent: [dispatch => function Intercept(opts, handler) {
const record = {
opts,
data: [],
};
(requests[opts.origin] ??= []).push(record);
return dispatch(opts, {
__proto__: handler,
onError(err) {
record.error = err;
return Reflect.apply(handler.onError, this, arguments);
},
onHeaders (statusCode, headersRaw) {
const headers = Object.create(null);
for (let i = 0; i < headersRaw.length;i += 2)
headers[headersRaw[i].toString()] = headersRaw[i + 1].toString();
Object.assign(record, {statusCode, headers});
return Reflect.apply(handler.onHeaders, this, arguments);
},
onData(chunk) {
record.data.push(chunk);
return Reflect.apply(handler.onData, this, arguments);
},
onComplete(trailers) {
record.trailers = trailers;
return Reflect.apply(handler.onComplete, this, arguments);
},
});
}],
},
});
setGlobalDispatcher(agent);
process.on(`exit`, () => {
fs.writeFileSync(nockFile, v8.serialize(requests));
});
break;
}
case `replay`:{
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
mockAgent.disableNetConnect();
const requests = v8.deserialize(fs.readFileSync(getNockFile()));
for (const origin in requests) {
const mockPool = mockAgent.get(origin);
for (const record of requests[origin]) {
const {opts: {path, method, headers}} = record;
const intercept = mockPool.intercept({path, method, headers});
if (record.error) {
intercept.replyWithError(record.error);
} else {
intercept.reply(record.statusCode, Buffer.concat(record.data), record);
}
}
}
break;
}
default:
}
Posting it here in case it can help someone else.
@aduh95 IIUC, this doesn't cover fetch
, right?
@mikicho, I believe that will cover Undici's fetch but not third-party implementations of fetch like whatwg-fetch
or node-fetch
. This is the main difference compared Undici's MockAgent
and what Interceptors in MSW are doing (we support both global fetch and fetch implementations over the http
module)
@kettanaito I agree. I'm just wondering if this somehow covers node.js native fetch,
which, behind the scenes, uses undici. from what I see, this is not the case because from what I see nodejs doesn't export this implementation detail outside.
We are currently discussing in undici to implement a function to replace the globals in nodejs with the ones of undici.
@aduh95 IIUC, this doesn't cover
fetch
, right?
It does cover Node.js built-in globalThis.fetch
(and not other fetch
implementations that are based on the node:http
module, as #2397 (comment) says)
@aduh95 Interesting! Why is it working? because AFAIK undici implementation doesn't exposed outside, so you can't even require
it, and I would expect that the internal undici and external one (that you install via npm
) wouldn't share state.
Imho the Symbol for the dispatcher is globally available, so using setGlobalDispatcher works against the node core undici
It works because of nodejs/undici#1405
Hey guys,
We have introduced experimental support for fetch
. Please share your feedback with us. You can install it by:
npm install --save-dev nock@beta
CC @piotrm221
wow, it worked out of the box on my CI!! This is awesome, thanks @mikicho !!
The beta is working nicely for me too for Jest tests in my Rails/TypeScript/Stimulus application
I had to modify the matchHeader('content-length', ...)
matchers, as they weren't working. Everything else worked without additional changes.
@Ugzuzg What modifications? convert to string?
The expected values were already string. The actual value was undefined
.
.matchHeader('content-length', val => val === '123')
, val
is undefined
.
I just changed the matchers not to use matchHeader
, didn't fix the issue itself.
@Ugzuzg Thanks!
Can you make a quick reproduction? because when I tried it yesterday, it worked for me, except it turned the value into a string.
I found a small inconsistency with current latest nock
.
Let's say I setup the following mock:
nock('http://0.0.0.0/')
.put('/myendpoint')
.reply(404, { body: '404 Not Found' })
Triggering it with the http module will return a Response
object with statusText: Not Found
, whereas the fetch version will end up with statusText: null
fetch response:
Response {
[Symbol(realm)]: { settingsObject: {} },
[Symbol(state)]: {
aborted: false,
rangeRequested: false,
timingAllowPassed: false,
requestIncludesCredentials: false,
type: 'default',
status: 404,
timingInfo: null,
cacheState: '',
statusText: 'null',
headersList: HeadersList {
cookies: null,
[Symbol(headers map)]: [Map],
[Symbol(headers map sorted)]: null
},
urlList: [],
body: { stream: undefined, source: null, length: null }
},
[Symbol(headers)]: HeadersList {
cookies: null,
[Symbol(headers map)]: Map(1) { 'content-type' => [Object] },
[Symbol(headers map sorted)]: null
}
}
When using --no-experimental-fetch
and a fetch
polyfill:
Response {
size: 0,
timeout: 0,
[Symbol(Body internals)]: {
body: <Buffer 5b 6f 62 6a 65 63 74 20 52 65 61 64 61 62 6c 65 53 74 72 65 61 6d 5d>,
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
url: undefined,
status: 404,
statusText: 'Not Found',
headers: Headers { [Symbol(map)]: [Object: null prototype] },
counter: undefined
}
}
fixed in #2588 (14.0.0-beta.3)
CC @perrin4869
that fixed my issue, thanks!
Works like a charm, thank you!
Thanks very much for your great work and supporting native fetch so fast! 🙏
I ran our tests based on nock back with the beta version and noticed 2 problems:
-
Nock no longer works for HTTP requests with
gzip
response encoding. For existing fixtures (generated with previous version) the binary data is returned instead of the extracted JSON data. -
It is no longer possible to record new nock fixtures. The generated JSON files contains just
[]
. If read correctly this is not supported yet, right?
- Can you please share an example of the
nock
line and thefetch
request? - Yes, currently, we don't support recording fetch request. We may add support for this later.
@mikicho sure
Here is the fetch request:
const reply = await fetch(
`${this.config.baseUrl}/api/1.23/feed?key=${this.config.apiKey}&assetId=${assetId}&pid=${pids}&mk=${context.shop.country}`,
{
method: 'GET',
signal: AbortSignal.timeout(this.config.timeout),
},
)
And here the recorded mock data from the current stable nock release
{
"scope": "https://api.channelsight.com:443",
"method": "GET",
"path": "/api/1.23/feed?key=<redacted>&assetId=<redacted>&pid=<redacted>&mk=<redacted>",
"body": "",
"status": 200,
"response": [
"1f8b0800000000000400edbd07601c499625262f6dca7b7f4af54ad7e074a10880601324d8904010ecc188cde692ec1d69472329ab2a81ca6556655d661640cced9dbcf7de7befbdf7de7befbdf7ba3b9d4e27f7dfff3f5c6664016cf6ce4adac99e2180aac81f3f7e7c1f3f22bef78b3f7a95b75951e6f5cbba9aada76df3d123faf0a42ca66fdfccebf55775f9d1a38fe66dbb6a1eddbd3bc5c7e3e93c5b2ef3b2292ee6ed785a2dee4eef4e1f66bb0f269fee6eef9ccfee6fefefecdcdf7ef8f0debded6c6f279fec9fefedd22777f34fa7079feedc9f6c4ff2e9eef67ebe4fdf7ffae9cef6834f77cecfb3ddfd87bb0fb3bb7b7bfbfbbbf94eb67defd349b6bdbf777f7ffbe0fcfedef683fb7b0f0f3ecd773f9dec7e7af77cff7eb67bff7cb2bdbb7b6f46dd7d7a7f3bdb39a08eb3bddd69befb90307978f7a3d147afdb6afaf6655d4c731916ff4de3395bca6fdae2797e99d33097ebb21c7dc4cd3f7ab433de197d74d63ccdcfb375d97ef4a8add7f9e8a3e759d37eb59a656d3e3ba60ff1c62ff93eda3dafa65959fc209f7df4e83c2b1b34a54fdaa25a9ed14704eacb769ed75fbd7a4e88e0add147c79744f66c5294457b1da0a408ec3fbc0f14bec8dae91c303eba0d8de9f5ce7cbec81604eca317bfd7b73fddbffff9f1ee17e9e765d6bccdeb6c51bc3dcfcb599acf682edb6c5ef65f7e9a37d3ba586118066dd38251fa747f2fcf766693ed4f679f4eb7f7f709a5834f3f7db8bd3f3d9f7dbafff0e0e1fefec483fa795dad57783104a5289eaeeb6a594c9ba7a7f4cab3bac897b3f2baf31d7d63de7a5db4f8c6b0e6d5d5d538d756e3598ed9372d9f571755878f67cb3e17d7dabca4e6cddd266f7fffddbdfb776f33c4f16a79e1f5773c6d8b4bc24d58c67c7a52ad976d8da9e601da8fd7759d2fa7f8fcf4ab57912f5ee7abaccedaaaa616e3c8f76fe6d5bac996b3c66f38a2ffba0d5f5f2f2615a8f09ffc217f1d7dfd7abd5a553531f2f36c79b1ce2e5444cc5f4af8cff37a912da9b9a27f52cdf031fd431f9160acebdc7eb44d038334988e8f67b33a6f086c38dd5fd0cfa7599b7177bf578e91bf9e57e08bd1473f99956b00dbddd97dc8b08e67f36a8ad6c76d5b179375cb687eff978cde5f47dd7ff860767ffffcc1f664e7e1a7dbfb070f0eb627f74907cdf24fcf1f9eef4e77eee7f77ea4a3de4b4731024e43dd86c2f4b261045532c269d261e7ab9bf4cff9fd7c3ac97766db0ff3dd9c287b30d93ed8dbbdb7fd60367db873b0b77f40d3e5757883fe29f3b76d5da5bfd73ffaf74ddfd264f494907e919ed2e0e7f4b579dfd344c47fac8804d4f6db758e37481f79cd3f481ddd66c43f5247fcd137a88e1e7c7aeff7fff4c1fdfd8fa077a4cdcb8a6c66c99db976fbbb07bb7b5ea327afbfcd3ce73529f25fc4580d2936fa8aba7ef4d16d1411815541e1579c8da72f9ed43409f4e193aa6166fd225baecfb3292824b2731b25d6794f278146d5f9e26c4153c4f85b8cb4ad0ad5f6ef554de78b75c9d4d21681707f74b690e94fcfeb6a919e3cfa7d7fdf134f22d26cb522bdcecaaaf97d7f5f69fc3aaf2f0b529bbfefef3b69e6bf7f93ad7edfdff7f3d3575fecbc1c9fbd7a7ef2ede3f1cb93f1eef8e9e9786f67f7d39ddddd839dbddd837b07e3770bb83a8a854822fdfd45562c791cfd4f8ecbf64dfe8e74a919ce5b1a0d7ca7514a0a7346436ff3755eb7f40e342aeb586f74200c99ab80638fdf5063fd484975bc6e881330a93d4ee6d61e44c3c3fae666acd2add337bbf7eedf2110469accfb5f14cb62b15e1ccf2ea95dd1e4b350afbb097e95934a5a90320c9b7469c8d4c270c540cbf07418248d3432f948f11669bec6e7dd1173ebff8f8ff8c5736aa01f29de2f7298d8924413f6251cf5b2dce6378647fd7bb1d74ef29ca76fabeaedaaccb236bdacaa3a3dafeae5ba00cc9fcb01f3a4e9478af2d014e7cb5b4cf194e4619aceabc928ad2ef3653aad0832d98c9fdb51b230ea478aeb90e8d228b9f5ff0747c9aca81f29ae9b989746ca6ffcbf7fa4ce449dcdf2655b9c17798d6f42f3b9bfb7bfb7b3b3f7f0febd7bfb7b1fd13b27e44f5f54e410a2ed2ffee8dbf456564fe786360a3235cd7ec0a68a00e907e40d1085f577f5e5cd4bc775ceaca3df1a88279fd387afd713b27afc790cc2d36a414aaf981ec33c664b3286f48e69a480be787a4c1fde00e859f3a4a056e6637df5097d74c38b4fd64db124b7ea59417a989a9bef15c28b7b7bf461000333fc4bf010cffd888cfae28fc888df0d20f337b534bfeaeb4488743b3d21ebd756abf45536a37ee07705000d697eae2823fdff5cf5de87f035e74586f1fffd71f401dd524c95003f671490fe7fae7aef43f89af49761fc7f7f1c7d40efc947e446b86c033e247fa9c82e96944a28a667cbf38a5c8befff92ef",
"ff3f81c6fbea95190000"
],
"rawHeaders": [
"Content-Type",
"application/json; charset=utf-8",
"Date",
"Mon, 18 Jul 2022 11:36:15 GMT",
"Access-Control-Expose-Headers",
"Request-Context",
"Cache-Control",
"no-cache",
"Content-Encoding",
"gzip",
"Expires",
"-1",
"Pragma",
"no-cache",
"Transfer-Encoding",
"chunked",
"Vary",
"Accept-Encoding",
"Strict-Transport-Security",
"max-age=604800",
"api-supported-versions",
"1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19, 1.20, 1.21, 1.22, 1.23, 1.24, 1.25, 1.26, 1.27, 1.28, 1.29, 1.30, 1.31, 1.32, 1.33, 1.34, 1.35, 1.36, 1.37, 1.38, 1.39, 1.40, 1.41, 1.42, 1.43, 1.44, 1.45, 1.46, 1.47, 1.48, 1.49",
"Request-Context",
"appId=cid-v1:fd0ef9e2-adfc-47bb-8837-0ba519863119"
],
"responseIsBinary": false
}
@mikicho works perfectly. Thanks very much! Now only recording support is missing to fully migrate
Hello there, I'm trying this fetch beta feature for a couple of weeks now.
It works fine in most cases but I noticed that I'm not able to simulate timeouts with nock on fetch requests.
the delai() method is not taken into account.
Am i missing something ?
@aannoune, fetch by itself doesn't support timeouts. If Nock ships an API that helps you mock a request timeout, then Nock has to provide that support explicitly by using an AbortController
and a custom timeout logic that triggers .abort()
when the timeout is reached.
@kettanaito
Why AbortController? Delay can also be about delaying a request for a specific time till return a 200 OK.
@Ugzuzg, the original question was about timeouts so that's what I assumed. You are right, for delaying responses you just have to take the mock delay into account, it's a different thing.
@Ugzuzg : i confirm that my primary goal is to trigger a timeout error in fetch request to test the handling of a request falling into timeout. I there any AbortController support planned in Nock ?
@aannoune Can you please share the code? (nock interceptor + fetch request)
@mikicho i cannot share the full code of the fetch request (it is not my own code but another squad's lib i must use) but it is called with a
{signal: AbortSignal.timeout(5000)}
in fetch options.
regarding the interceptor : it is a rather simple one like
nock("https://fakUrl").get(':myPass').delay(10000).reply(500)
that i would expect to make my fetch call return a timeout error
This is great. I'd love to see fetch support added to recording as well.
Hi @mikicho, thanks for adding support for native fetch!
I haven't been able to pinpoint the actual problem yet on my side (I don't think this is a nock problem per se, so I didn't want to make an issue), but when trying to use nock in my jest tests, I get an error when nock tries to patch global.fetch
:
TypeError: Cannot assign to read only property 'fetch' of object '[object global]'
The output of Object.getOwnPropertyDescriptor(global, 'fetch')
shows that it's not writable to begin with:
{
value: [Function: fetch],
writable: false,
enumerable: true,
configurable: true
}
Have you seen properties of global
not being writable
by default when doing patching of the global object in nock?
I also wanted to add to this discussion since it's possible someone else has run into this/will run into this.
@mkurapov Interesting. Is there something else that may override fetch
before Nock does?
This is great. I'd love to see fetch support added to recording as well.
Recording is well needed. Like when you nocking a 3rd party library and you don't even know which requests are happening, recording is the only way to go.
Unfortunately at this moment on the project I'm working nock is just recording an empty array :(
@mikicho it was a result of having an older version of jest. I was on jest 29.5.0, but upgrading to 29.7.0 fixes the problem.