Closed deshiknaves closed 3 years ago
I haven't had a chance to look at this issue in its entirety, but I recently did some troubleshooting on another cypress-related project. It may help you out - https://github.com/msutkowski/msw-cypress-repro. If you don't get a better answer by tomorrow afternoon, I'll look into it :)
@msutkowski that example expects it to be loaded as part of the application (index.tsx). It works if I do that. However, what I want to achieve is to make msw
not a dependency of the application, but add it in as part of Cypress. It's adding it in onBeforeLoad
, this way, I can make a global server in Cypress, then add custom commands to set up routes just like fixtures
work in Cypress. It is getting loaded and available in the window, but the route doesn't get intercepted.
Also https://github.com/deshiknaves/cypress-playground/tree/feature/msw-cypress that branch currently has the proof of concept.
Ah, there is another issue where @kettanaito had an idea about making it a command. I'm not 100% sure on the Cypress execution context & timing there, but you're possibly running into race conditions even when using onBeforeLoad
. I'd have to read up more on the Cypress-specific behavior here. When I made the repro I linked, the tests still seemed to be flaky with Cypress no matter what I did.
I've tried putting a massive delay and it still doesn't pick it up. I'll also dig into this a bit more. In my example, the it's definitely available on the window. And for me it's not flaky, it never gets intercepted.
Hi guys, the problem here is that there are multiple clients to same worker.
1) Cypress is a client 2) React app is another client
Under the hood MSW save registered clients to mock requests only for them.
The problem in this case for this approach is that request are sent by react while the only "real" client is cypress
Yep, I just noticed this by logging MOCK_ACTIVATE
action and it's id is 4ebd2cd3-3a0b-46c8-8513-d5d0b354c85b
while later on it's 96a4641b-f87c-4fbd-9f11-39ca3f5fc1e6
.
Any ideas on what can be done to make something like this possible? IMHO this would make it a lot more useful.
This is entirely a hack and I haven't understood the implications yet. But what I've done is cached the connected clientId
during MOCK_ACTIVATE
. Then change the code when getting the client:
const client = await event.target.clients.get(
connectedClientId || clientId,
)
if (
// Bypass mocking when no clients active
!client ||
// Bypass mocking if the current client has mocking disabled
!clients[connectedClientId] ||
// Bypass mocking for navigation requests
request.mode === 'navigate'
) {
return resolve(getOriginalResponse())
}
Now it works exactly as I expect it to.
I will spend more time understanding everything that goes on in this file. But is there a way to either flag to the SW that this is the client to use or anything else that makes sense?
In the same branch that I mentioned above, I have a working example of this being abstracted into a command using the modified serviceworker.
There are a couple of things that I have questions on. If there is a solution that works after that, I'd be happy to work on a PR to make this work:
Would love to hear your thoughts as you have a lot more context on this.
However, what I want to achieve is to make msw not a dependency of the application, but add it in as part of Cypress.
This totally makes sense. The primary reason we are using msw is to use the same mocks in our unit and e2e tests, but integrating them into source just doesn't make sense as a pattern to me.
There's a gotcha that hasn't been called out here too, worker.start()
returns a promise. As far as I can tell, there is no way to await a promise in onBeforeLoad
.
One possible solution to this would be a babel macro to conditionally add them to the app. This breaks some of the advantages to the approach you have here though, co-location, totally separated from the application source code, etc.
Could there be any possibility to add options to setupWorker
or worker.start
in a unified|host
mode where this would act as the client?/host for all clients connected? It would be useful in scenarios like testing.
As for worker.start()
not being awaited. In the example repo this is happening in a before()
in support/index.js
and not in every test. So it happens before everything and is awaitable.
before(async () => {
worker = setupWorker()
await worker.start()
})
One possible solution to this would be a babel macro to conditionally add them to the app.
I ended up creating something that could enable this.
https://github.com/zapplebee/perenv.macro
This allows you to use environmental variables to conditionally import things in your app.
So it:
As an example:
- index.js
- mocks/
- first_set.js
- second_set.js
- cypress/
- integration/
- spec1.js
- spec2.js
index.js
import { loadPerEnv } from "./perEnv.macro";
loadPerEnv('./mocks/first_set.js', 'MSW_MOCKS', 'FIRST');
loadPerEnv('./mocks/second_set.js', 'MSW_MOCKS', 'SECOND');
bootstrapApp()
package.json
{
"scripts" : {
"start:mocks1": "MSW_MOCKS=FIRST react-scripts start",
"test:mocks1": "cypress run --spec \"cypress/integration/spec1.js\"",
"e2e:mocks1": "start-server-and-test start:mocks1 http://localhost:3000 test:mocks1",
"start:mocks2": "MSW_MOCKS=SECOND react-scripts start",
"test:mocks2": "cypress run --spec \"cypress/integration/spec2.js\"",
"e2e:mocks2": "start-server-and-test start:mocks1 http://localhost:3000 test:mocks1",
}
}
Even writing this out, I know this is not ideal, but it does work around the need to modify msw in any way.
Yep, that would work — I can already do this if it's included as part of the application. I think there should be a way to add msw
as part of Cypress
without modifying the host application in any way. There are many reasons for this. The app can have its only handlers that it wants to use as part of the development experience. It would be great if Cypress
can load it's own instance and mock the requests per each test without the app having any knowledge of it.
I'll code up a solution this weekend and get everyone's opinion on that take.
I wonder if Cypress can do anything to control the clientId, or at least know the clientId of the JS application.
If msw exposed an API to register a client (either as yourself or on behalf of another client), that would make this possible too.
Another approach might be to use cy.stub()
and stub fetch or XHR request. That way, the calls could be lifted up into the Cypress context instead of the application context.
The problem with that approach is now you're modifying the application code. The goal is to leave the application as it is and test it as such just mocking its response, not the mechanism.
The clientId
isn't readily available from what I can see, and also requires an API change.
I think by spinning up an interceptor to be the host for all clients connected to it could be a simple and elegant solution.
any news on this? @deshiknaves
I believe the implementation that solves this issue is under review. It's been in that state for quite some time as we don't have the capacity to tackle all the issues, so some end up postponed.
We do wish for a better Cypress integration, but we also wish to retain a clear and clean internal implementation. That often requires a scrupulous code review. We have not forgotten about this issue and the associated pull request, and how to look into it in the nearest future.
If you need this merged sooner, I strongly encourage you to review the pull request, understand what is the suggested solution, voice your concerns. A team behind MSW is small, so we need each bit of help we can get in pushing the improvements like this forward. Hope for your understanding on this matter.
I've been a little bit busy lately, but I will make sometime on this one. I need a bit of input from the the team. I'll wait to hear back on some of the questions, and then tackle some of the suggestions from @kettanaito
Update: we kicked off a solution to properly intercept requests issued from iframes (nested clients). In the current state of the pull request, MSW is able to intercept and mock a response to a request that originates from an iframe on the page. With @deshiknaves help we found out that in the case of Cypress there may be multiple nested clients involved, which we still need to ensure in a form of an integration test.
Also https://github.com/deshiknaves/cypress-playground/tree/feature/msw-cypress that branch currently has the proof of concept.
@deshiknaves I checked out your branch and it didn't work
Also https://github.com/deshiknaves/cypress-playground/tree/feature/msw-cypress that branch currently has the proof of concept.
@deshiknaves I checked out your branch and it didn't work
@bochen2014 thats really old. https://github.com/deshiknaves/cypress-msw-interceptor – has a lot of what was being discussed here.
I'm not sure where the best place to ask questions, so I'm asking them here. This is a simple example and I'll abstract it more when I can get the simple example working. What I'm trying to do is load MSW as part of the Cypress tests and not part of the application itself. This will allow me use use MSW to mock any part of the application — just like
fixtures
do in Cypress. The benefit being that MSW allows me to inspect the request before responding, something Cypress doesn't allow at the moment.Environment
Request handlers
Actual request
Current behavior
I can see from the console that MSW was started and that it is available on the
window
. The request says it went though the serviceworker, but it actually get the response back from the API and not MSW.Expected behavior
I was hoping it would be intercepted by MSW and the response sent back. Obviously something about the manner in which I'm doing this doesn't work. It would be great to get some input as to why, maybe there is some other way I can do this.
Response should be
Screenshots