Unit Testing React components with Cypress
Tags: cypress, react, unittest, componenttest
Published: 2020-05-19 06:24:00

_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](https://github.com/NoriSte/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](https://www.cypress.io/blog/2022/06/01/cypress-10-release/), 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](https://docs.cypress.io/guides/component-testing/introduction#What-is-Component-Testing) support, check it out! And other exciting news is on the way thanks to the [Storybook 6.2 release](https://twitter.com/NoriSte/status/1378204109841571840)!_

---

Two months ago, I wrote the “[Testing a Virtual List component with Cypress and Storybook](https://dev.to/noriste/testing-a-virtual-list-component-with-cypress-and-storybook-3lam)” 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](https://storybook.js.org/) in action in your team (if not, consider adding it!)

* you could be not familiar with testing components with [Testing Library](https://testing-library.com/) 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](https://www.cypress.io/) or [TestCafé](https://devexpress.github.io/testcafe/) (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](https://dev.to/noriste/testing-a-virtual-list-component-with-cypress-and-storybook-3lam), 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](https://itnext.io/cypress-storybook-keeping-test-scenario-data-and-component-rendering-in-one-place-c57b23cc1640) 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](https://github.com/bahmutov/cypress-react-unit-test) and [cypress-vue-unit-test](https://github.com/bahmutov/cypress-vue-unit-test) repos.

What does it mean? That Cypress can now directly mount a React component giving the **[cypress-react-unit-test](https://github.com/bahmutov/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](https://dev.to/noriste/testing-a-virtual-list-component-with-cypress-and-storybook-3lam). We need to set up both the [cypress-react-unit-test](https://github.com/bahmutov/cypress-react-unit-test) and the TypeScript conversion (the component is written in TypeScript, it is part of a [Lerna](https://github.com/lerna/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](https://github.com/bahmutov/cypress-react-unit-test#install), 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](https://stackoverflow.com/a/60017105/700707), so all I had to do is:

* adding a *cypress/webpack.config.js* file

```js
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
```json
{
  "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](https://github.com/NoriSte/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
```json
{
  "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à:
```typescript
/// <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
```js
/// <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](https://testing-library.com/docs/react-testing-library/api)

Nothing more, the Cypress test continues the same as the Storybook-related one 😊

### Callback Testing

Porting all the tests from the [previous article](https://dev.to/noriste/testing-a-virtual-list-component-with-cypress-and-storybook-3lam) 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
```typescript
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](https://sinonjs.org/releases/v9.0.2/stubs/)/[Spy](https://sinonjs.org/releases/v9.0.2/spies/) documentation for the full APIs.

## Conclusions

Here a screenshot of the last test, the most complete one

![The test runner reporting the test steps on the left and the rendered VirtualList on the right.](https://dev-to-uploads.s3.amazonaws.com/i/xw13de8h3yhg67rpzgiw.png "The last test with the stub checks.")_The last test with the stub checks._

and this is the recording of all the tests

{% youtube yVL-yRT5hFQ %}

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](https://github.com/bahmutov/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](https://dev.to/noriste/testing-a-virtual-list-component-with-cypress-and-storybook-3lam)

- UI Testing classification: [Component vs (UI) Integration vs E2E tests](https://dev.to/noriste/component-vs-ui-integration-vs-e2e-tests-3i0d)

- Avoid slowing down your tests with unnecessary and unpredictable waitings: [Await, do not make your E2E tests sleep](https://dev.to/noriste/await-do-not-make-your-e2e-tests-sleep-4g1o)