-
-
Notifications
You must be signed in to change notification settings - Fork 490
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
Add an option to warn/error on unhandled requests #257
Add an option to warn/error on unhandled requests #257
Conversation
Will throwing an error on these lines display an error message as intended or should I do |
src/node/setupServer.ts
Outdated
if (resolvedOptions.onUnhandledRequest === 'warn') { | ||
// Produce a developer-friendly warning | ||
return console.warn( | ||
`A request to ${mockedRequest.url} was detected but not mocked because no request handler matching the URL exists.`, | ||
) | ||
} | ||
|
||
if (resolvedOptions.onUnhandledRequest === 'error') { | ||
// Throw an exception | ||
|
||
// throw new Error(`A request to ${req.url} was detected but not mocked because no request handler matching the URL exists.`) | ||
return | ||
} | ||
|
||
if (typeof resolvedOptions.onUnhandledRequest === 'function') { | ||
resolvedOptions.onUnhandledRequest(mockedRequest) | ||
return | ||
} | ||
|
||
// resolvedOptions.onUnhandledRequest === 'bypass' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would personally avoid duplication of both the error / warn message and the onUnhandledRequest
logic, by creating a util, like:
export const onMissingRequestHandler = (
request: MockedRequest,
onUnhandledRequest: OnUnhandledRequest,
) => {
if (typeof onUnhandledRequest === 'function') {
onUnhandledRequest(request)
return
}
if (['warn', 'error'].includes(onUnhandledRequest)) {
const message = `[MSW] A request to ${request.url} was detected but not mocked because no request handler matching the URL exists.`
if (onUnhandledRequest === 'warn') {
console.warn(message)
} else {
throw new Error(message)
}
return
}
// if onUnhandledRequest is 'bypass' we have nothing to do.
}
I've added [MSW]
tag at the beginning of the message and refactored a bit the logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
src/utils/handleRequestWith.ts
Outdated
if (options.onUnhandledRequest === 'warn') { | ||
// Produce a developer-friendly warning | ||
console.warn( | ||
`A request to ${req.url} was detected but not mocked because no request handler matching the URL exists.`, | ||
) | ||
return channel.send({ type: 'MOCK_NOT_FOUND' }) | ||
} | ||
|
||
if (options.onUnhandledRequest === 'error') { | ||
// Throw an exception | ||
|
||
// throw new Error(`A request to ${req.url} was detected but not mocked because no request handler matching the URL exists.`) | ||
return channel.send({ type: 'MOCK_NOT_FOUND' }) | ||
} | ||
|
||
if (typeof options.onUnhandledRequest === 'function') { | ||
options.onUnhandledRequest(req) | ||
return channel.send({ type: 'MOCK_NOT_FOUND' }) | ||
} | ||
|
||
// options.onUnhandledRequest === 'bypass' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can avoid channel.send
duplication and onUnhandledRequest
logic by creating the util described in a previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure of how much is the effort, but we should test this logic on both worker.listen
and server.start
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Would you be willing to write the tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely agree, there's no need to duplicate the channel.send()
and any other possible logic in case of an unhandled request. You can think of the new feature as:
if (!response) {
onUnhandledRequest(req, resolvedOptions) // analyzes "resolvedOptions.onUnhandledRequest"
return channel.send({ type: 'MOCK_NOT_FOUND' })
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can try!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify: I mean exactly what @cloud-walker suggested above: a custom function that encapsulates the logic (checks if the option value is a function, or one of the enums).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Would you be willing to write the tests?
How can contribute in this scenario? You give fork permissions to me? Or I should do another fork? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will give you permission to push to my fork
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've merged the changes made by @kettanaito in the upstream repository so we're all up to date and I've given you access. Can you verify that it works? Haven't done this before.
src/node/glossary.ts
Outdated
import { OnUnhandledRequest } from '../onUnhandledRequest' | ||
|
||
export interface ListenOptions { | ||
onUnhandledRequest?: OnUnhandledRequest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering we will be shipping the same behavior in terms of this option to both Browser and Node, do you find it useful to introduce a concept of "shared" configuration?
interface SharedOptions {
onUnhandledRequest?: OnUnhandledRequest
}
type WorkerOptions = SharedOptions & {
serviceWorker: WhatWeHaveNow
// ...
}
type ServerOptions = SharedOptions
All those interfaces, apart from the SharedOptions
, already exist in the respective setupWorker
/setupServer
declarations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we could call it IsomorphicOptions
? Doesn't really matter to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Shared" seems to describe that the options are shared between multiple units. I'm not sure what "isomorphic" means in relation to those settings.
src/node/setupServer.ts
Outdated
@@ -10,6 +10,11 @@ import { getResponse } from '../utils/getResponse' | |||
import { parseRequestBody } from '../utils/parseRequestBody' | |||
import { isNodeProcess } from '../utils/isNodeProcess' | |||
import * as requestHandlerUtils from '../utils/requestHandlerUtils' | |||
import { ListenOptions } from './glossary' | |||
|
|||
const DEFAULT_LISTEN_OPTIONS: DeepRequired<ListenOptions> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to use DeepRequired
, it's one level object (for now).
I added most of the changes that you requested, but there is still one problem: due to the use of |
@kettanaito I've no idea on how to run the integration tests, running You have any advice on how to start? Thanks! |
They could be failing because of TypeScript errors? |
This change fixes the TypeScript issues and probably also solves the issue with the integration tests for @cloud-walker , but it does make the code a little less DRY. Please give your opinion on this change and whether you have a better suggestion. |
You've done an amazing work, fellows! I've rebased the branch and will add some tests to cover this feature. Looks solid! |
Awesome! |
I'm having troubles testing the
The culprit is that the
|
@kettanaito you can share the code of the tests? |
Sure. I've pushed the
|
I've pushed browser integration tests. The following tests are failing, I cannot seem to figure why:
Manual testing produces expected behavior. There must be something on the Puppeteer's/test setup side that fails to assert that behavior properly. If somebody could take a look at those tests, I'd be much thankful! |
I'm not an expert of puppeteer, but I can give an eye 👀 @kettanaito The command to run is |
@cloud-walker that'd be awesome. Yeah, to run a specific integration test use: $ yarn test:integration test/msw-api/setup-worker/start/on-unhandled-request/warn.test.ts You can also run a specific test scenario in a browser to play around with by this command: $ yarn test:focused test/msw-api/setup-worker/start/on-unhandled-request/warn.mocks.ts For some reason both error and callback-throws scenarios fail (they don't produce an error in the console during runtime). Manual testing works as expected. Something must be off. |
Have you run |
@kettanaito I get a My guess is that some cleanup of puppeteer is missing, but I'm not an expert of it T_T |
Yes, you need to |
I use MacOS mojave |
There's one nuance I've noticed: since all requests come through Service Worker and MSW, if you specify I can't say I disagree with such behavior, since the |
Perhaps it would be better to include a required RegEx option with |
I personally think it's the expected behavior, at least for For |
@jensmeindertsma, I'm not against supporting a RegExp as the value of Regarding making the // Handled request
rest.get('/user', (req, res) => res()),
// Fall-through handler matching any other request
res.get('*', (req, res) => res.networkError('Any other request raises a network error!') The |
Is there still work to be done to get the current version of this feature shipped? |
There are two test suits that fail:
We need to either construct them better, or make sure they pass in the current state before shipping this. |
@kettanaito you think it's possible to reproduce the bugged scenarios on a dummy repo? Just to remove the real project noise? |
I'm not sure, @cloud-walker. That noise may contain a valuable context. Once we bring a potentially solved test scenario in, it may start failing anyway. I'd say it's a good practice to approach tests exactly where they're being run. This certainly takes more time, but I'm confident it's worth it. Sometimes Puppeteer is tricky, but most of the times it's just me now knowing how to use it. For example, I wonder why it doesn't get the console errors that are clearly there if you run Puppeteer in a headfull mode. The overall I'm a strong believer that no test is a thousand times better than a flaky one. I'll give this one more go, and if I cannot find a reliable way to test certain error scenarios they simply won't be added. |
After significant amount of effort I conclude
I'm removing those tests and checking if the CI passes with the remaining browser and server tests. I've still added the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you everybody for participating in developing this feature!
It looks great, let's publish it and give it a try. Approved 🎉
🎉 |
This enables us to use onUnhandledRequest introduced in mswjs/msw#257
* chore(dependencies): bump msw to 0.20.5 This enables us to use onUnhandledRequest introduced in mswjs/msw#257 * chore(msw setup): throw on un-intercepted requests * fix(reports/apiService): errors during submit are not being detected as such We could only detect this by stop mocking fetch and letting msw intercept the request on the network level. Now we know the apiService and the thunk invoking it did not rethrow the error caught by ky. * test(reports): test successful submit using msw closes https://github.com/FixMyBerlin/fixmy.platform/issues/250 relates to https://github.com/FixMyBerlin/fixmy.platform/issues/424 * chore(dependencies): update jest and ts.jest to v26 * chore(jest setup): cleanup * refactor(OverviewMapState unit tests): replace fetch-mock with msw * refactor(OverviewMapState unit tests): increase readability * refactor(SubmitReportState unit tests): increase readability * refactor(survey unit tests): replace fetch-mock with msw * refactor(api unit tests): replace fetch-mock with msw * chore(jest setup): only use one setup file This simplifies our setup as it has been desired. It makes not much sense to split up the logic running before each test into two files. It is not 100% clear, but setupFilesAfterEnv seems to offer everything setupFiles does PLUS globals like `expect` or `beforeAll` can be used. See https://stackoverflow.com/questions/47587689/whats-the-difference-between-setupfiles-and-setuptestframeworkscriptfile * Fix article unit test * Add new fetch error to expected actions * Revert commenting out code * fix(OverviewMapState tests) bugs where introduced in 8a49c12 and e2583aa * refactor(SubmitReportState.unit.test): prettier Co-authored-by: Vincent Ahrend <mail@vincentahrend.com>
GitHub
Specification
This specification will be kept up to date as we iterate over the functionality.
An unhandled request is a request that has no corresponding request handler. It's possible to configure MSW to handle such requests differently. For example, MSW could notify the user with a warning or error message when a request has no corresponding request handler.
onUnhandledRequest
Both worker and server instances accept the
onUnhanledRequest
configuration option. This allows the user to choose a provided functionality or provide their own through a custom function:Important: all functionality is opt-in. This means that the default value for
onUnhanledRequest
is'bypass'
.Usage example
Draft
This pull request is a work in progress. That means large portions of functionality have not been implemented yet, and the functionality and specification details are still open for debate.