nock / nock

HTTP server mocking and expectations library for Node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Node 18+: make `nock` work native `fetch`

johnb8005 opened this issue · comments

Please avoid duplicates

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

Dup of #2183 and #2336

For now, set --no-experiemental-fetch flag

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 native fetch 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

image

@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 think node:stream may even have a utility to convert Fetch API ReadableStream into a regular Node stream to then pipe into the ClientRequest.
  • Request's Headers as OutgoingHeaders. You can use transformer functions from the headers-polyfill to transform one into another.
  • Request's signal and AbortController via the native options.signal on the http.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:

  1. 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.

  2. 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?

@holzerch

  1. Can you please share an example of the nock line and the fetch request?
  2. 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
  } 

@holzerch Fixed in #2591 (14.0.0-beta.4)

@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.

The beta is working nicely for uvu + probot. Great job team!

@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.

@aannoune After some thinking, it seems like the "fake delay" feature does not make sense in the same way it does with the http module API.
Meanwhile, we recommend you use time fakers to mimic the same behavior.

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?
image

commented

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.