Cypress 4.5.0 is out with a long-awaited feature: first-class support for framework’s components render.
I’m working on a big UI Testing Best Practices project on GitHub, I share this post to spread it and have direct feedback.
UPDATE: Cypress 10 is out with Component Testing integrated with E2E testing, please check it out and ignore all the configuration steps reported below since they are outdated!
UPDATE: Cypress 7 is out with a brand-new Component Test support, check it out! And other exciting news is on the way thanks to the Storybook 6.2 release!
Two months ago, I wrote the “Testing a Virtual List component with Cypress and Storybook” article. This is an extending article now that unit testing React component is possible with Cypress.
The goal of the previous article was to run some experiments in the React Component Testing world, a really important topic nowadays.
The motivations were pretty simple:
you probably already have Storybook in action in your team (if not, consider adding it!)
you could be not familiar with testing components with Testing Library or you could be biased about JSDom or you could want to test your UI components in a real browser, not in a simulated DOM environment
you could be familiar with Cypress or TestCafé (if not, consider them for your UI tests) and you could want to use just a single tool for your tests
And the approach was simple too:
exposing the story’ props to the testing tool, used to control the rendered component
pick up them from Cypress/TestCafé, automating user actions and asserting about the contents of the props
But there were some caveats…
performance: in the article, I put some extra-efforts to minimize the impact of story switching slowness
testing and stories coupling: since Storybook is consumed even by Cypress, stories are going to be accountable not only for sharing the design system across the team but for the component tests too
callback testing got tough: checking the params and the calls of the callback props is difficult
Some of the problems of my experiment could be mitigated by Dmitriy Tishin approach but the solution is not optimal yet, but then…
Cypress 4.5.0 has been released
On April, 28th, Cypress 4.5.0 has been released, the only released feature is the following
Cypress now supports the execution of component tests using framework-specific adaptors when setting the experimentalComponentTesting configuration option to true. For more details see the cypress-react-unit-test and cypress-vue-unit-test repos.
What does it mean? That Cypress can now directly mount a React component giving the cypress-react-unit-test a new birth! Before Cypress 4.5.0 release, the plugin was pretty limited but now it has first-class support! In fact, the cypress-react-unit-test is now rock-solid and a meaningful plugin.
Testing the VirtualList component: second episode
The component is always the same, the VirtualList, read more about it in the previous article. We need to set up both the cypress-react-unit-test and the TypeScript conversion (the component is written in TypeScript, it is part of a Lerna monorepo, and it is compiled with Webpack). Both the steps are straightforward but if the plugin has an installation-dedicated section in its documentation, the TypeScript compilation could not be obvious because there are, outdated or partial, a lot of different approaches and resources.
The most concise yet effective solution is André Pena’s one, so all I had to do is:
- adding a cypress/webpack.config.js file
module.exports = {
mode: 'development',
devtool: false,
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader',
options: {
// skip typechecking for speed
transpileOnly: true,
},
},
],
},
],
},
}
- adding a cypress/tsconfig.json file
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress", "cypress-wait-until"]
}
}
please note that:
the ../tsconfig.json file is the same used by the React app
cypress-wait-until is not mandatory but I use it a lot and it is one of the most installed plugins for Cypress
The above transpiling-related files, along with the following cypress.json file
{
"experimentalComponentTesting": true,
"componentFolder": "cypress/component"
}
are enough to start playing with a cypress/component/VirtualList.spec.tsx test! From the previous article, the first test was the standard rendering, the “When the component receives 10000 items, then only the minimum number of items are rendered” test, et voilà:
/// <reference types="Cypress" />
/// <reference types="cypress-wait-until" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'
import { VirtualList } from '../../src/atoms/VirtualList'
import { getStoryItems } from '../../stories/atoms/VirtualList/utils'
describe('VirtualList', () => {
it('When the list receives 10000 items, then only the minimum number of them are rendered', () => {
// Arrange
const itemsAmount = 10000
const itemHeight = 30
const listHeight = 300
const items = getStoryItems({ amount: itemsAmount })
const visibleItemsAmount = listHeight / itemHeight
// Act
mount(
<VirtualList
items={items}
getItemHeights={() => itemHeight}
RenderItem={createRenderItem({ height: itemHeight })}
listHeight={listHeight}
/>,
)
// Assert
const visibleItems = items.slice(0, visibleItemsAmount - 1)
itemsShouldBeVisible(visibleItems)
// first not-rendered item check
cy.findByText(getItemText(items[visibleItemsAmount]))
.should('not.exist')
})
})
Compared to the Storybook-related article:
- the
/// <reference types="Cypress" />
/// <reference types="cypress-wait-until" />
at the beginning are needed to let VSCode correctly leverage TypeScript suggestions and error reporting (it works for plain JavaScript files too)
- we use cypress-react-unit-test’ mount API to mount the component, nothing especially new if you are used to the Testing Library APIs
Nothing more, the Cypress test continues the same as the Storybook-related one 😊
Callback Testing
Porting all the tests from the previous article is quite easy, what was missing is the callback testing part of the “selection test”.
Creating a WithSelectionManagement wrapper component that renders the VirtualList one and manages items selection is quite easy and we can pass it our stub and assert about it
it('When the items are clicked, then they are selected', () => {
const itemHeight = 30
const listHeight = 300
let testItems
const WithSelectionManagement: React.FC<{
testHandleSelect: (newSelectedIds: ItemId[]) => {}
}> = props => {
const { testHandleSelect } = props
const items = getStoryItems({ amount: 10000 })
const [selectedItems, setSelectedItems] = React.useState<(string | number)[]>([])
const handleSelect = React.useCallback<(params: OnSelectCallbackParams<StoryItem>) => void>(
({ newSelectedIds }) => {
setSelectedItems(newSelectedIds)
testHandleSelect(newSelectedIds)
},
[setSelectedItems, testHandleSelect],
)
React.useEffect(() => {
testItems = items
}, [items])
return (
<VirtualList
items={items}
getItemHeights={() => itemHeight}
listHeight={listHeight}
RenderItem={createSelectableRenderItem({ height: itemHeight })}
selectedItemIds={selectedItems}
onSelect={handleSelect}
/>
)
}
WithSelectionManagement.displayName = 'WithSelectionManagement'
mount(<WithSelectionManagement testHandleSelect={cy.stub().as('handleSelect')} />)
cy.then(() => expect(testItems).to.have.length.greaterThan(0))
cy.wrap(testItems).then(() => {
cy.findByText(getItemText(testItems[0])).click()
cy.get('@handleSelect').should(stub => {
expect(stub).to.have.been.calledOnce
expect(stub).to.have.been.calledWith([testItems[0].id])
})
})
})
Please refer to the full SinonJS (wrapped and used by Cypress) Stub/Spy documentation for the full APIs.
Conclusions
Here a screenshot of the last test, the most complete one
The last test with the stub checks.
and this is the recording of all the tests
The test lasts now less than seven seconds, without depending nor loading Storybook, leveraging first-class Cypress support.
What’s next? The cypress-react-unit-test plugin is quite stable and useful now, a whole new world of experiments is open and a lot of small-to-medium projects could choose to leverage Cypress as a single testing tool. I’m waiting for your comments and experience 😊
Related articles
Other articles of mine you would find interesting:
The original component testing experiment with Cypress and Storybook Testing a Virtual List component with Cypress and Storybook
UI Testing classification: Component vs (UI) Integration vs E2E tests
Avoid slowing down your tests with unnecessary and unpredictable waitings: Await, do not make your E2E tests sleep
Top comments (2)
Of course the day I need to upgrade my react cypress testing suite to 4.5, you've updated this guide with the April updates. Thank you so so much! Will post with any difficulties / findings that others might find helpful as well.
We'll have to see how coverage plays in, I've had so much trouble getting interaction + unit tests to both contribute to one cohesive report.
Thanks, waiting for your findings, Ben 😊