Skip to content

Latest commit

 

History

History
234 lines (169 loc) · 10 KB

cypress-react-component-test.md

File metadata and controls

234 lines (169 loc) · 10 KB

Unit Testing React components with Cypress

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 Storybook 6.2 release!


Now that unit testing React component is possible with Cypress, this is an extending chapter of the Testing a component with Cypress and Storybook one.

The goal of the previous chapter 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 chapter, 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 daedalius 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 chapter. 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 chapter, 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 '[@testing](http://twitter.com/testing)-library/cypress/add-commands'

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 chapter:

  • the
/// <reference types="Cypress" />
/// <reference types="cypress-wait-until" />

at the beginning are needed to let VSCode correctly leverage TypeScript suggestions and error reporting

  • 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 chapter 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](http://twitter.com/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

Take a look at the result in this video. 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.



Crossposted by NoriSte on dev.to and Medium.