Let's take a button component that directs the browser to an external site.
1 | export const LoginBtn = () => { |
🎁 You can find the full source code shown in this blog post in the repo bahmutov/stub-location. Even better, this example and lots more React Cypress component tests are shown in the huge repo muratkeremozcan/cypress-react-component-test-examples.
Our test clicks on the button from a Cypress component test.
1 | import { LoginBtn } from './Login' |

Ughh, we really don't want to visit the other domain from this button component test. Can we prevent the transition by stubbing the location.assign('https://cypress.tips') method call?
Unfortunately not. If we try to use cy.stub to wrap the location.assign method, it fails
1 | cy.mount(<LoginBtn />) |

The browser prevents overwriting the location methods for security purposes. The location.assign method is not writable or configurable.
Logging
Let's take a step back from testing the Login button, and instead consider what would you do if I asked you to add logging every time the location changes? Can you log every call to location.assign? What if there are several places in the application call that invoke location.assign? How would you refactor you code? A common solution is to introduce your own wrapper object for window.location and call its methods. So let's introduce a tiny object Location that wraps around window.location methods we use.
1 | import setupDebug from 'debug' |
We update all calls in our source to call Location.assign instead of location.assign
1 | import { Location } from './Location' |
We can see these calls logged in the browser, even during our test
1 | cy.mount(<LoginBtn />) |
We can see the debug calls if we set localStorage.debug=location

Location is testable
We introduced the Location.ts object to wrap around the native unstubbable location methods. This Location object gives us another benefit - its methods are stubbable.
1 | import { LoginBtn } from './Login' |

End-to-End tests
We can make our Location methods stubs work from end-to-end tests. Instead of importing the Location object from the src/Location.ts source file, we "save" the reference to this singleton object on the window object when running Cypress tests. Modify the Location code:
1 | import setupDebug from 'debug' |
In our Cypress E2E test:
1 | it('stubs the location.assign method', { baseUrl: 'http://localhost:3000' }, () => { |
The test runs beautifully.

Nice!