A user has recently asked in the Cypress Discord channel how to write a component test for a component that executes the following code window.location.host.split(".")
. Grabbing the window.location
is a side-effect and is generally unavailable from a component test. It would work great in an end-to-end test, of course, but we need to make it work right now.
Any time you have a piece of code that gives you problems, move it into its own function or source file and stub it from the Cypress test to bypass the problem. Of course, you want to stub the smallest piece of your code to make sure you still test the most of it, see the blog post Stub The Form That Opens The Second Browser Tab that shows it nicely. Let's see how the same principle could work for a React component test.
I have started the a new React application using the create-react-app
to scaffold it.
🎁 You can find the complete source code at bahmutov/stub-react-import.
1 | $ npx create-react-app stub-react-import |
I installed Cypress and Prettier
1 | $ npm i -D cypress prettier |
I love using Prettier to format my code. Let's configure the Cypress component testing.
Cypress wizard suggests the following cypress.config.js file
1 | const { defineConfig } = require('cypress') |
The component
Let's put have our App.js
show the host and the path
1 | import logo from './logo.svg' |
End-to-end test
First, let's confirm the component is showing the expected host and path when running in an end-to-end test. Our test is simple:
1 | /// <reference types="cypress" /> |
Component test
Nice, let's take a look at our component. Without any changes to the source code, our component test could be
1 | /// <reference types="cypress" /> |
Ughh, the component runs in the test window, thus it shows the spec's pathname. Can we simply stub the window.location
? Not really, we cannot redefine the window.location
property, it is pretty locked down in the browser for security reasons.
1 | it('shows the location host and path', () => { |
Move the side effect
Let's start by moving the problematic code that accesses the window.location
object to its own source file.
1 | export const getLocation = () => { |
The App.js
imports the getLocation
function and calls it to get the window.location
properties.
1 | import { getLocation } from './Location' |
Stub the import
Now we need to add one more plugin @babel/plugin-transform-modules-commonjs
to our development dependencies for our tests to work
1 | $ npm i -D @babel/plugin-transform-modules-commonjs |
We have relied on the default Webpack settings found in the repository to bundle each component during Cypress component tests. Now we need to insert the @babel/plugin-transform-modules-commonjs
into the transpiling pipeline. Thus I will expand the cypress.config.js file to pass the full Webpack config
1 | const { defineConfig } = require('cypress') |
The plugin's loose: true
option makes all imports accessible from other files, thus a spec file can stub an import and the stub will be used in the source files. If you run the component test again, nothing should change. But now let's modify our component test and stub the getLocation
import
1 | /// <reference types="cypress" /> |
We can make the test stricter by confirming our getLocation
stub was used and the test has not passed accidentally.
1 | /// <reference types="cypress" /> |
Happy stubbing!