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!