12 Recipes for testing React applications using cypress-react-unit-test
Published: 2020-06-29 13:19:06

---
title: 12 Recipes for testing React applications using cypress-react-unit-test
published: true
description: 12 common testing situations when working with React
tags: 
//cover_image: https://direct_url_to_image.jpg
---

**Note:** this blog post takes the twelve testing examples from [12 Recipes for testing React applications using Testing Library](https://dev.to/jooforja/12-recipes-for-testing-react-applications-using-testing-library-1bh2) blog post by [João Forja](https://dev.to/jooforja), where the same examples are tested using testing-library. This blog post uses [cypress-react-unit-test](https://github.com/bahmutov/cypress-react-unit-test) + Cypress combination to test exactly the same scenarios.

**Note 2:** you can find these tests in the repo [bahmutov/12-testing-recipes](https://github.com/bahmutov/12-testing-recipes)

## Table of Contents

1. [Invokes given callback](#invokes-given-callback)
2. [Changes current route](#changes-current-route)
3. [Higher Order Component](#higher-order-component)
4. [Component cleans up on unmount](#component-cleans-up-on-unmount)
5. [Depends on Context Provider](#depends-on-context-provider)
6. [Uses functions that depend on time](#uses-functions-that depend-on-time)
7. [Custom hooks](#custom-hooks)
8. [Portal](#portal)
9. [Focus is on correct element](#focus-is-on-correct-element)
10. [Order of elements](#order-of-elements)
11. [Selected option](#selected-option)
12. [Dynamic page titles](#dynamic-page-titles)

## Invokes given callback

* We're testing that after some interaction the component calls a given callback.
* We give a mock function to the component under test and interact with it so that it calls the callback. Then we assert we called the function with the expected parameters. If relevant, we also check the number of times the function was called.

```js
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

describe('Invoke callback', () => {
  function Button({ action }) {
    return <button onClick={() => action()}>Call</button>
  }

  it('callback is called on button click', () => {
    const callback = cy.stub()
    mount(<Button action={callback} />)

    cy.contains('button', /call/i)
      .click()
      .then(() => {
        expect(callback).to.have.been.calledOnce
        expect(callback).to.have.been.calledWithExactly()
      })
  })
})
```
The test runs and we can see the stub count of 1 in the Cypress' Command Log on the left.

![Click invokes the stub we have passed to the component](https://dev-to-uploads.s3.amazonaws.com/i/6uxlv16rflo6751o6pd7.png)

The same test can be written differently avoiding `.then` by saving the reference to the stub using an alias.

```js
it('callback is called on button click using an alias', () => {
  mount(<Button action={cy.stub().as('callback')} />)

  cy.contains('button', /call/i).click()
  cy.get('@callback')
    .should('have.been.calledOnce')
    .and('have.been.calledWithExactly')
})
```

![Prop handler saved as an alias](https://dev-to-uploads.s3.amazonaws.com/i/hj6zxd4ua2n6hd4us1tk.png)

## Changes current route
- We're testing that the component redirects the user to an expected router with the expected query parameters after an interaction.
- We first create a routing environment similar to that in which we'll use the component. We set up that environment so we can capture the URL to which the component will redirect us. We interact with the component to cause the redirect. We then assert that we were redirected to the URL we expected.

```js
/// <reference types="cypress" />
import React, { useState } from 'react'
import { MemoryRouter, Route, useHistory } from 'react-router-dom'
import { mount } from 'cypress-react-unit-test'

describe('Changes current route', () => {
  it('On search redirects to new route', () => {
    let location
    mount(
      <MemoryRouter initialEntries={['/']}>
        <Route path="/">
          <SearchBar />
        </Route>
        <Route
          path="/*"
          render={({ location: loc }) => {
            location = loc
            return null
          }}
        />
      </MemoryRouter>,
    )

    cy.get('input#query').type('react')
    cy.get('input[type=submit]')
      .click()
      .then(() => {
        expect(location.pathname).to.equal('/search-results')
        const searchParams = new URLSearchParams(location.search)
        expect(searchParams.has('query')).to.be.true
        expect(searchParams.get('query')).to.equal('react')
      })
  })
})

function SearchBar() {
  const history = useHistory()
  const [query, setQuery] = useState('')

  return (
    <form
      onSubmit={function redirectToResultsPage(e) {
        debugger
        e.preventDefault()
        history.push(`/search-results?query=${query}`)
      }}
    >
      <label htmlFor="query">search</label>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.currentTarget.value)}
        id="query"
      />
      <input type="submit" value="go" />
    </form>
  )
}
```

You can see where the test is typing and the button it is clicking

![Routes test](https://dev-to-uploads.s3.amazonaws.com/i/0ihy2u496q8297quvjkz.gif)

## Higher Order Component

* We're testing that a HOC gives the props we expect to the wrapped component.
* We first create a mock component for the HOC to wrap. The mock component will store the received props in a variable. After rendering the component returned by the HOC we assert that it gave the mock component the props we expected.

```js
import React from 'react'
import { mount } from 'cypress-react-unit-test'

function withSum(WrappedComponent, numbersToSum) {
  const sum = numbersToSum.reduce((a, b) => a + b, 0)
  return () => <WrappedComponent sum={sum} />
}

describe('Higher Order Component', () => {
  it('Adds number and gives result as a prop', () => {
    let result
    function WrappedComponent({ sum }) {
      result = sum
      return null
    }
    const ComponentWithSum = withSum(WrappedComponent, [4, 6])
    mount(<ComponentWithSum />)

    // mount is an asynchronous command
    cy.then(() => {
      expect(result).to.equal(10)
    })
  })
})
```
![Higher order component test](https://dev-to-uploads.s3.amazonaws.com/i/991q66pz8lezkpttxhas.png)

Seeing "Unknown" due to anonymous function in the mounting message weird, we can give it a label:
```js
mount(<ComponentWithSum />, { alias: 'ComponentWithSum' })
```
![Mounted component with an alias](https://dev-to-uploads.s3.amazonaws.com/i/0bli9psvkgqtt57ljgnd.png)

## Component cleans up on unmount

- We want to assert that a component subscribes after mount and unsubscribes after unmount.
- We start by mocking the subscription methods so we can assert they get called. We then render the component and assert that it subscribed. All that's left to do is make the component unmount and assert it unsubscribed.

```js
/// <reference types="cypress" />
import React, { useEffect } from 'react'
import { mount, unmount } from 'cypress-react-unit-test'

function ComponentThatSubscribes({ subscriptionService }) {
  useEffect(() => {
    subscriptionService.subscribe()
    return () => subscriptionService.unsubscribe()
  }, [subscriptionService])
  return null
}

describe('Component cleans up on unmount', () => {
  it('Subscribes and unsubscribes when appropriate', () => {
    const subscriptionService = {
      subscribe: cy.stub().as('subscribe'),
      unsubscribe: cy.stub().as('unsubscribe'),
    }

    mount(<ComponentThatSubscribes subscriptionService={subscriptionService} />)

    cy.get('@subscribe')
      .should('have.been.calledOnce')
      .and('have.been.calledWithExactly')

    unmount()

    cy.get('@unsubscribe')
      .should('have.been.calledOnce')
      .and('have.been.calledWithExactly')
  })
})
```

Again, both `cy.stub` instances can be found in the Command Log.

![Mount and unmount callbacks](https://dev-to-uploads.s3.amazonaws.com/i/uga3g7jnkdc1kqh2odau.png)

## Depends on Context Provider

- We want to test a component that depends on a context Provider
- To test the component, we'll recreate the environment in which we'll use the component. In other words, we'll wrap the component in the Context Provider.

```js
/// <reference types="cypress" />
import React, { useContext } from 'react'
import { mount } from 'cypress-react-unit-test'

const UserContext = React.createContext()

function UserFullName() {
  const { user } = useContext(UserContext)
  return <p>{user.fullName}</p>
}

describe('Proivder', () => {
  it('displays name of current user', () => {
    mount(
      <UserContext.Provider value={{ user: { fullName: 'Giorno Giovanna' } }}>
        <UserFullName />
      </UserContext.Provider>,
    )
    cy.contains('Giorno Giovanna').should('be.visible')
  })
})
```
The test passes, and if we hover over the "contains" command, the found element is highlighted on the page.

![Highlighted element found using cy.contains command](https://dev-to-uploads.s3.amazonaws.com/i/kcgq0borudwii1jnheb4.png)

## Uses functions that depend on time

- We want to test a component that depends on real-time. In this example, that dependency comes from using setTimeout().
- When testing components that depend on real-time, we need to be aware that those tests shouldn't take too long. ~~One way to do that is to have the component receive the time interval as a prop to allow us to configure a shorter time interval for tests than we would have in production.~~ One way to do so that is to mock the clock during the test to speed things up.

```js
/// <reference types="cypress" />
import React, { useState, useEffect } from 'react'
import { mount } from 'cypress-react-unit-test'

function TrafficLight() {
  const timeUntilChange = 500
  const [light, setLight] = useState('Red')
  useEffect(() => {
    setTimeout(() => setLight('Green'), timeUntilChange)
  }, [timeUntilChange])
  return <p>{light}</p>
}

describe('Functions that depend on time', () => {
  it('Changes from red to green to after timeout', () => {
    cy.clock()
    mount(<TrafficLight />)

    cy.contains(/red/i).should('be.visible')
    cy.tick(500)
    cy.contains(/green/i).should('be.visible')
  })
})
```

We are using [cy.clock](https://on.cypress.io/clock) to freeze the application's clock. After checking if the light is red at the beginning, we fast-forward the timer by 500ms and check the light again. This time it shows "Green". We can confirm the DOM structure during each step by using the built-in Cypress time-traveling debugger.

![Inspecting the component](https://dev-to-uploads.s3.amazonaws.com/i/qt9qacv5nmzgyodncb71.gif)

## Custom hooks
- We want to test a custom hook.
- Since we're testing a hook, we'll need to call it inside a component otherwise we'll get an error. Therefore, we'll create a mock component, use the hook inside it, and store what the hook returns in a variable. Now we can assert what we need to assert using that variable.
```js
/// <reference types="cypress" />
import React, { useState, useCallback } from 'react'
import { mount } from 'cypress-react-unit-test'

function useCounter() {
  const [count, setCount] = useState(0)
  const increment = useCallback(() => setCount((x) => x + 1), [])
  return { count, increment }
}

describe('Hooks', () => {
  it('counter increments', () => {
    let counter
    function MockComponent() {
      counter = useCounter()
      return null
    }

    mount(<MockComponent />)
      .then(() => {
        expect(counter.count).to.equal(0)
        counter.increment()
      })
      .then(() => {
        expect(counter.count).to.equal(1)
      })
  })
})
```
![Testing a component with hooks](https://dev-to-uploads.s3.amazonaws.com/i/b6bouwhuk3tzwflwtlql.png)

You can also test the hook directly - `cypress-react-unit-test` will create a mock component for you.

```js
import { useState, useCallback } from 'react'
import { mountHook } from 'cypress-react-unit-test'

function useCounter() {
  const [count, setCount] = useState(0)
  const increment = useCallback(() => setCount(x => x + 1), [])
  return { count, increment }
}

describe('useCounter hook', function() {
  it('increments the count', function() {
    mountHook(() => useCounter()).then(result => {
      expect(result.current.count).to.equal(0)
      result.current.increment()
      expect(result.current.count).to.equal(1)
      result.current.increment()
      expect(result.current.count).to.equal(2)
    })
  })
})
```

![Testing React hook use mountHook function](https://dev-to-uploads.s3.amazonaws.com/i/l5zl377b0yrjkojtgag3.png)

## Portal
- We want to test a component that's a portal.
- A portal needs a DOM node to be rendered into. So to test it, we'll have to create that DOM node. After we make the assertions, we'll have to remove the DOM node as not to affect other tests.

```js
/// <reference types="cypress" />
import React, { useRef, useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
import { mount } from 'cypress-react-unit-test'

function PortalCounter() {
  const el = useRef(document.createElement('div'))
  const [count, setCount] = useState(0)

  useEffect(() => {
    const modalRoot = document.getElementById('modal-root')
    const currentEl = el.current
    modalRoot.appendChild(currentEl)
    return () => modalRoot.removeChild(currentEl)
  }, [])

  return ReactDOM.createPortal(
    <>
      <section aria-live="polite">
        count: <span data-testid="counter">{count}</span>
      </section>
      <button type="button" onClick={() => setCount((c) => c + 1)}>
        inc
      </button>
    </>,
    el.current,
  )
}

describe('Portal', () => {
  it('PortalCounter starts at 0 and increments', () => {
    const modalRoot = document.createElement('div')
    modalRoot.setAttribute('id', 'modal-root')
    document.body.appendChild(modalRoot)

    mount(<PortalCounter />)
    cy.contains('[data-testid=counter]', 0)
    cy.contains('button[type=button]', 'inc').click()
    cy.contains('[data-testid=counter]', 1)
  })
})
```
![Testing the portal component](https://dev-to-uploads.s3.amazonaws.com/i/u7qo5knflvbn1e2afcp2.png)

## Focus is on correct element

- We want to test that the focus on the element we expect.
- We can verify if an element has focus or not by using `.should('have.focus')` assertion.

```js
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

function NameForm() {
  return (
    <form>
      <label htmlFor="name">Name</label>
      <input id="name" type="text" />
    </form>
  )
}

describe('Focus is on correct element', () => {
  it('clicking on label gives focus to name input', () => {
    mount(<NameForm />)

    cy.contains('label', 'Name').click()
    cy.get('input#name').should('have.focus')
  })
})
```
![Confirming the clicked input has the focus](https://dev-to-uploads.s3.amazonaws.com/i/5ggs7x38v3e27b3lodp6.png)

## Order of elements
- We want to test that a list of elements is rendered in the expected order.
- We'll take advantage of [cy.get](https://on.cypress.io/get) queries returning elements in the order in which they appear on the HTML.
- It's important to note that this approach doesn't take into account CSS that might change the order in which the elements are displayed.

```js
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

function NamesList({ names }) {
  return (
    <ul>
      {names.map((name) => (
        <li key={name}>{name}</li>
      ))}
    </ul>
  )
}

describe('Order of elements', () => {
  it('renders names in given order', () => {
    const names = ['Bucciarati', 'Abbacchio', 'Narancia']

    mount(<NamesList names={names} />)
    cy.get('li').should(($li) => {
      expect($li[0]).to.have.text(names[0])
      expect($li[1]).to.have.text(names[1])
      expect($li[2]).to.have.text(names[2])
    })
  })
})
```
![Comparing text in each element](https://dev-to-uploads.s3.amazonaws.com/i/ofeom7lnngmowa99xabi.png)

In Cypress, you can see the component renders the elements in the expected order and then use [visual testing](https://on.cypress.io/visual-testing) to automatically validate it.

You can further simplify the above test using [.each](https://on.cypress.io/each) command

```js
cy.get('li').each((li, k) => {
  expect(li).to.have.text(names[k])
})
```

## Selected Option
- We want to test that an input is checked.
- We can use `should('be.checked')` to test if an element is checked.

```js
/// <reference types="cypress" />
import React from 'react'
import { mount } from 'cypress-react-unit-test'

function SeasonsForm() {
  return (
    <form>
      <p>Best season:</p>
      <section>
        <input name="season" type="radio" id="winter" value="winter" />
        <label htmlFor="winter">Winter</label>
        <input name="season" type="radio" id="spring" value="spring" />
        <label htmlFor="spring">Spring</label>
        <input
          name="season"
          checked
          readOnly
          type="radio"
          id="summer"
          value="summer"
        />
        <label htmlFor="summer">Summer</label>
        <input name="season" type="radio" id="autumn" value="autumn" />
        <label htmlFor="autumn">Autumn</label>
      </section>
    </form>
  )
}

describe('Selected option', () => {
  it('Has Summer pre-selected', () => {
    mount(<SeasonsForm />)
    cy.get('input[type=radio]#summer').should('be.checked')
  })
})
```
![Selected option test](https://dev-to-uploads.s3.amazonaws.com/i/ftlmewj2ahoovyh8joy0.png)

## Dynamic page titles
- We want to test that the title of the current page is updated.
- We access the current title by using [cy.title](https://on.cypress.io/title). Even if the document title won't be immediately updated, Cypress will [automatically waiting and retrying](https://on.cypress.io/retry-ability) the assertion `should('equal', '1')`.

```js
/// <reference types="cypress" />
import React, { useState } from 'react'
import { Helmet } from 'react-helmet'
import { mount } from 'cypress-react-unit-test'

function DocTitleCounter() {
  const [counter, setCounter] = useState(0)

  return (
    <>
      <Helmet>
        <title>{String(counter)}</title>
      </Helmet>
      <button onClick={() => setCounter((c) => c + 1)}>inc</button>
    </>
  )
}

describe('Document title', () => {
  it('Increments document title', () => {
    mount(<DocTitleCounter />)
    cy.title().should('equal', '0')
    cy.contains('button', /inc/i).click()
    cy.title().should('equal', '1')
  })
})
```
![Document title test](https://dev-to-uploads.s3.amazonaws.com/i/ijyor9nbxp2rs6awq816.png)

## Other resources

- The original blog post [12 Recipes for testing React applications using Testing Library](https://dev.to/jooforja/12-recipes-for-testing-react-applications-using-testing-library-1bh2#portal) where the above 12 examples came from
- More [cypress-react-unit-test examples](https://github.com/bahmutov/cypress-react-unit-test#examples)
- [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/)