-
Notifications
You must be signed in to change notification settings - Fork 781
Default Node httpClient
configuration does not get mocked by MSW nor upcoming Nock version
#2211
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
Comments
@kanadgupta thanks for the super detailed report! We'll take a look and see what our options are here. The fix seems straightforward, but we don't want to break anything else in the process. (filed internally as: http://go/j/DEVSDK-2261) |
@kanadgupta thanks so much for the detailed explanation and for the workaround! I was really having issues with the MSW and Stripe calls hanging. Thanks again🍺 |
@iamgutz Where you able to intercept the requests with MSW? Nothing works for us. We tried creating our own request client, using the Using Stripe's HTTP client just ignores MSW - the requests always hit the real backend. Using import http from 'node:http';
import https from 'node:https';
import Stripe from 'stripe';
import invariant from 'tiny-invariant';
const { STRIPE_SECRET_KEY } = process.env;
invariant(STRIPE_SECRET_KEY, 'STRIPE_SECRET_KEY is not set');
// Why is this needed?
// See: https://github.com/nock/nock/issues/2785#issuecomment-2427076034
const isTestEnvironment = Boolean(process.env.CI ?? process.env.VITEST);
// /**
// * Patch `module_.request` so that headers are flushed immediately
// * (MSW’s interceptor can see them before Stripe defers `.end()`).
// */
// function patchFlush(module_: typeof http | typeof https) {
// const orig = module_.request;
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// module_.request = function (options: any, callback: any) {
// const request = orig.call(this, options, callback);
// if (typeof request.flushHeaders === 'function') {
// request.flushHeaders();
// }
// return request;
// };
// }
// function patchEnd(module_: typeof http | typeof https) {
// const original = module_.request;
// module_.request = function (options: any, callback: any) {
// const request: any = original.call(this, options, callback);
// request.on('socket', (socket: any) => {
// // instead of waiting for socket.connect, just end it now
// request.end();
// });
// return request;
// };
// }
// if (isTestEnvironment) {
// patchFlush(http);
// patchFlush(https);
// patchEnd(http);
// patchEnd(https);
// }
class Client implements Stripe.HttpClient {
async makeRequest(
url: string,
options: Stripe.RequestOptions,
): Promise<Stripe.HttpClient.Response> {
return fetch(url, options);
}
getClientName(): string {
return 'Stripe-Node-HTTP-Client';
}
}
export const stripeAdmin = new Stripe(STRIPE_SECRET_KEY, {
httpClient: isTestEnvironment ? new Client() : undefined,
}); This is what we get using the "flush version"
With a mock like this: const createCustomerMock = http.post(
'https://api.stripe.com/v1/customers',
async ({ request }) => {
console.log('createCustomerMock', request);
// 1. Read the raw request body as text…
const bodyText = await request.text(); // :contentReference[oaicite:0]{index=0}
console.log('bodyText', bodyText);
// 2. …and parse that URL-encoded string into fields
const form = new URLSearchParams(bodyText); // :contentReference[oaicite:1]{index=1}
const email = form.get('email');
const description = form.get('description');
// etc.
// 3. Grab any headers you need
const authHeader = request.headers.get('Authorization');
const stripeVersion = request.headers.get('Stripe-Version');
// 4. Return a mock 201 response with JSON
return HttpResponse.json(
{
id: 'cus_mocked_123',
email,
description,
},
{
status: 201,
},
);
},
); |
Okay, for anyone finding this issue, this works for us: import Stripe from 'stripe';
import invariant from 'tiny-invariant';
const { STRIPE_SECRET_KEY } = process.env;
invariant(STRIPE_SECRET_KEY, 'STRIPE_SECRET_KEY is not set');
// Why is this needed?
// See: https://github.com/nock/nock/issues/2785#issuecomment-2427076034
const isTestEnvironment = Boolean(process.env.CI ?? process.env.VITEST);
/**
* A passthrough wrapper around the global `fetch` function.
*
* This is necessary because passing `fetch` directly to
* `Stripe.createFetchHttpClient` does not guarantee correct `this` binding
* in all environments (such as Node.js or test runners).
*
* In particular, in certain test environments (e.g., using MSW, Nock, or when
* mocks are applied), passing `fetch` point-free (i.e., just `fetch`) may
* result in `this` being undefined, leading to unexpected errors like
* `TypeError: Illegal invocation`.
*
* Wrapping `fetch` inside a new function (`passthroughFetch`) ensures:
* - Correct argument forwarding
* - Proper binding of `this` context (implicitly bound to `globalThis`)
* - More predictable async behavior across environments
*
* See also: https://github.com/nock/nock/issues/2785#issuecomment-2427076034
*
* @param args - The arguments to pass to `fetch`, matching
* `Parameters<typeof fetch>`.
* @returns A `Promise<Response>` from calling the global `fetch`.
*/
const passthroughFetch = (...args: Parameters<typeof fetch>) => fetch(...args);
export const stripeAdmin = new Stripe(STRIPE_SECRET_KEY, {
httpClient: isTestEnvironment
? Stripe.createFetchHttpClient(passthroughFetch)
: undefined,
}); |
It's worth pointing out that there are four possible reasons MSW/Interceptors/Nock doesn't intercept a request:
|
Describe the bug
Hi there. I'm using
nock@beta
and it is no longer able to intercept requests from this library when using the default Node.jshttpClient
settings (i.e., this HTTP client).This is because
nock
is adding support for nativefetch
and now uses@mswjs/interceptors
to intercept requests (which is also used bymsw
).The workaround is fairly trivial — configure the Stripe SDK to use
fetch
(see theworkaround
section here). But oncenock@14
is released, it will not support this library without additional end user configuration. Based on a handful of issues in this repo, I cannot imagine this is the desired behavior:#1844
#1854
#1866
To Reproduce
See https://github.com/kanadgupta/nock-beta-stripe-sdk
Expected behavior
The current Nock beta and its underlying interceptor library (
@mswjs/interceptors
) should be able to mock requests from the Stripe SDK without having to set thehttpClient
configuration option.Code snippets
No response
OS
macOS
Node version
Node v20.16.0
Library version
stripe-node 17.2.1
API version
2024-09-30.acacia
Additional context
I initially filed a bug report in the
nock
repo: nock/nock#2785It appears that this is happening due to a known limitation where
@mswjs/interceptors
is unable to intercept certain requests withnode:http
. The maintainer documented a way to fix this here: mswjs/msw#2259 (comment)The text was updated successfully, but these errors were encountered: