Skip to content

Conversation

mikicho
Copy link
Contributor

@mikicho mikicho commented Sep 11, 2023

Why?

closes #2397

Parity Gaps

Nock is a mature and battle-proof project and used by a lot of developers over the years. therefore, Nock covers many real-life scenarios and takes care of a LOT of edge cases.
@mswjs/interceptors is great, but it still has some gaps to cover in order to reach Nock's level of maturity and ability to cover its wide test suite.

I adjust as much as I can Nock's code to @mswjs/interceptors logic and current capabilities and opened tickets in the repo to track issues and feature requests:

Failed tests list: mswjs/interceptors#575

Topics to discuss about

During the work, I gathered a few topics we need to discuss further. I'll open dedicated issue(s) for them in Nock's repo.
CC @gr2m @kettanaito

@gr2m
Copy link
Member

gr2m commented Sep 14, 2023

Sorry I'm not sure when I get to review it, I'll try to find time on Saturday.

If we manage to replace nock's on interceptors with the one from @mswjs while keeping all tests passing then I'm all for it

lib/intercept.js Outdated
return overriddenRequest(options, callback)
const req = convertFetchRequestToClientRequest(request);
req.on('response', response => {
request.respondWith(new Response('test', { status: 200 }))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: we'd want to read the response stream to the ReadableStream of the response instance and call request.respondWith() on the end event of the original response here.

As I mentioned in the issue, let me know if exporting this existing utility from Interceptors would help here. I think it would.

Copy link
Contributor Author

@mikicho mikicho Sep 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an idea how we can wait for the nockResponse end event? I use a promise, but maybe you had something else in mind.

As I mentioned in the issue, let me know if exporting this existing utility from Interceptors would help here. I think it would.

Thanks!! For now, I copied the file. We can update this later.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, we can rely on the internal http.ClientRequest implementation to guarantee the order of event execution.

const stream = new Readable()
const fetchResponse = new Response(stream, {
  status: response.statusCode,
  statusText: response.statusMessage,
  headers: response.headers // pseudo-code
})

response.on('data', (chunk) => stream.write(chunk))
response.on('end', () => {
  stream.finish()
  request.respndWith(fetchResponse)
})

This is a gist. If you want the actual implementation example, take a look at this function.

lib/intercept.js Outdated
)
overrideClientRequest()
const interceptor = new BatchInterceptor({
name: 'my-interceptor',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving this a meaningful name may improve the logging output, afaik. Maybe name: 'nock-interceptor'?

lib/intercept.js Outdated
host: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + url.search,
headers: fetchRequest.headers,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Node expects OutgoingHeaders here, which is not compatible with the Headers instance. You should be fine doing this:

headers: Object.fromEntries(fetchRequest.headers.entries())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch! 🙏

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just be cautious about the compilation target here. I'm not sure what's the support range is for Nock, but .fromEntries() and .entries() methods may not exist on super old versions of ECMAScript and Node.

Michael Solomon added 2 commits September 18, 2023 20:15
* @param {IncomingMessage} message
*/
function createResponse(message) {
const readable = new ReadableStream({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the version of Node Nock uses doesn't have the ReadableStream global. I'd double-check if this isn't the linter's problem as the first measure.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes the linter doesn't play well with relatively newly added globals. This can be modified in the linter's globals key, if talking about ESLint.

Copy link
Contributor Author

@mikicho mikicho Sep 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!
I think we can fix this after we will solve the got error. Most of Nock's tests are based on it and I'm eager to solve it and run all tests to find cases that we haven't covered yet.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to look into the got issue this week based on my availability. Right now, the best next step is to reliably reproduce it in an automated test. I've already written one (see mswjs/interceptors#432) but you've provided some input that I need to reflect in the test.

Copy link

@kettanaito kettanaito Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed the got issue, would appreciate a quick review of the pull request!

@mikicho
Copy link
Contributor Author

mikicho commented Sep 20, 2023

@kettanaito
Until we resolve the got problem, I workaround the problem in my local machine and work on fixing the failing test 💪

Maybe you have an idea of how I can propagate errors from the interceptors? Specifically, I'm trying to fix this test. I tried something like:

interceptor.on('request', function ({ request, requestId }) {
  ...
  const nockRequest = convertFetchRequestToClientRequest(request);
  nockRequest.on('error', error => {
    request.respondWith(new Response('put here the real error'))
    resolve()
  })
  ...
})

But it stuck.

@kettanaito
Copy link

@mikicho, you can throw in the request listener but that will be considered a request error. Try it, see if it produces the result you need.

interceptor.on('request', () => {
  nockRequest.on('error', (error) => {
    throw error
  })
})

@mikicho
Copy link
Contributor Author

mikicho commented Sep 20, 2023

@kettanaito
The test fails with Uncaught Error, I'd expect msw to catch the error and emit an error event, which later gets caught by the listener in the test
image

@kettanaito
Copy link

Oh, I see. Try responding with Response.error()?

request.respondWith(Response.error(YOUR_ERROR_MESSAGE))

This will instruct Interceptors to treat this as an intentional error, and it will forward it to the appropriate error handling logic based on the request module, like emitting the error event in case of http.ClientRequest.

@mikicho
Copy link
Contributor Author

mikicho commented Sep 21, 2023

image
test_intercept almost done! Most of the failures have a PR in @msw/interceptors repo, but a couple still need to be reviewed and addressed. We will handle them soon. :)

Next test file, I'm coming 🥣

@mikicho
Copy link
Contributor Author

mikicho commented Sep 23, 2023

We are making progress and moving forward!
image

@mikicho
Copy link
Contributor Author

mikicho commented Sep 24, 2023

💪
image

@gr2m
Copy link
Member

gr2m commented Sep 24, 2023

Just want to say thank you @mikicho and @kettanaito for working on this 💐💝

@kettanaito
Copy link

@gr2m, all praise is rightfully @mikicho's! I'm happy that Nock's test suite helps us uncover missing bits to the Interceptors and improve the library. All I want is the best interception for the end developer and this collaboration helps achieve just that.

@mikicho
Copy link
Contributor Author

mikicho commented Sep 25, 2023

@kettanaito Thank you for your willingness to help and I apologize for any clutter I may cause!
With your assistance, we can support both the 'nock' and 'msw' approaches to mocking with a robust foundation!

@mikicho
Copy link
Contributor Author

mikicho commented Sep 26, 2023

@gr2m Why does Nock expose the request object via the response ("res.req")
https://github.com/nock/nock/blob/main/tests/test_define.js#L232
This is not part of the IncomingMessage object API.

@mikicho
Copy link
Contributor Author

mikicho commented Sep 26, 2023

@gr2m Also, we currently pass the rawHeaders as-is. according to the docs, the rawHeaders is string of array.
While we support arrays, objects, and maps in the reply function, we don't stringify the values.
WDYT? This is a breaking change. I think it is a bug, and the response is invalid and misleading.

@kettanaito
Copy link

Why does Nock expose the request object via the response ("res.req")

I believe it's not part of the public API but Node.js associates the OutgoingMessage with the IncomingMessage that way. I do recall seeing something like that recently during testing, so you may be seeing the same thing. It's highly likely Node's internals.

@mikicho
Copy link
Contributor Author

mikicho commented Jul 4, 2024

Ok, so as developers tend to do... this PR led to a re-write of the mswjs/interceptors Node.js backend (😅) and @kettanaito (one of a kind! ❤️) just released this! 🎉

@gr2m @Uzlopak
Most of Nock's features are working!!
There are a few things to solve, and we track them on this ticket.
The only significant parity gap we have is the preemptive connection delay feature we have that is currently not supported because of API consideration on mswjs side. IMO, we should remove the support for this feature for now (just the preemptive, not the delay itself, of course). We may have it back in the future if it makes sense. (more context)
Personally, I want to merge this PR to the beta channel right after I solve a small problem we have in the recorder order (I have a solution, but I'm trying to solve it without the need to make the record function async
WDYT?

(even if it looks the same as the previous picture I shared, we had a lot of progress :))
image

Michael Solomon added 2 commits July 6, 2024 16:54
@mikicho
Copy link
Contributor Author

mikicho commented Jul 6, 2024

image

I summed up what is left here.

@mikicho mikicho marked this pull request as ready for review July 13, 2024 00:00
@mikicho mikicho changed the base branch from main to beta July 13, 2024 00:01
@mikicho mikicho changed the title Use @mswjs/interceptors for mocking - WIP Use @mswjs/interceptors for mocking Jul 13, 2024
@mikicho
Copy link
Contributor Author

mikicho commented Jul 13, 2024

@nock/maintainers
There are 21 skipped tests, but I think this is ready to merge into the beta channel.
I wrote in the change log what is left before we can merge this to main and the breaking changes.

Tests fail due to insufficient coverage, which IMHO we should ignore now. A lot of code has been changed, and I want to do a big cleanup, which I think will bring the coverage back to 100% :)

@gr2m
Copy link
Member

gr2m commented Jul 13, 2024

Sounds good to me 🥳 thank you for all your hard work on this 💐❤️

@gr2m gr2m merged commit 5c8c4c2 into nock:beta Jul 13, 2024
Copy link

🎉 This PR is included in version 14.0.0-beta.8 🎉

The release is available on:

Your semantic-release bot 📦🚀

Copy link

🎉 This PR is included in version 13.5.5 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Undici support Node 18+: make nock work native fetch
7 participants