How to access Redux state in Cypress

2 minutes read

Set up our application

I borrowed the code from react-redux.js.org which showcases react + redux in action using a todo list application.

Set up and scaffold Cypress

Cypress is a fantastic testing framework. It is easy to set up and can be picked up pretty quickly.

Setting up Cypress is pretty straight forward - just run:

$ npm install cypress
or
$ yarn add cypress

We will also install the recommeded dependency

$ npm install -D start-server-and-test

start-server-and-test is a cool tool that basically

Starts server, waits for URL, then runs test command; when the tests end, shuts down server

as explained on their github repo.

How to access the store in Cypress

We don’t have access to store() object ordinarily, but cypress can access window() object. We use this to attach the store to window in our index.js.

src/index.js
import React from 'react'
import ReactDOM from 'react-dom'

import { Provider } from 'react-redux'
import store from './redux/store'

import TodoApp from './TodoApp'

import * as serviceWorker from './serviceWorker'

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
)

if (window.Cypress) {
  window.store = store
}

Now we have access to store() and more importantly the state inside it.

Writing the test

Now that we have the store available, we can access redux state at

cy.window().its('store').invoke('getState')

The final version of the test will look like this, with redux code highlighted.

./cypress/integration/redux-test.js
/// 

describe('Tests functionality and redux state', () => {
  it('Successfully uses todo application', () => {
    cy.visit('/')

      // assertions on view, tabs and redux
      .get('[data-cy=Header]')
      .should('have.text', 'Todo List')
      .get('.add-todo')
      .should('have.text', 'Add Todo')
      .get('.todo-list')
      .should('have.text', 'No todos, yay!')
      .get('.visibility-filters')
      .should('include.text', 'all')
      .get('.visibility-filters')
      .should('include.text', 'completed')
      .get('.visibility-filters')
      .should('include.text', 'incomplete')

      .window()
      .its('store')
      .invoke('getState')
      .then((state) => {
        expect(state.visibilityFilter).to.be.a('string').and.equal('all')
        expect(state.todos.allIds).to.be.a('array').and.to.be.empty
        expect(state.todos.byIds).to.be.a('object').and.to.be.empty
      })

      // add a todo, add another todo , mark the first one as complete
      .get('input')
      .type('My First Todo')
      .get('.add-todo')
      .click()
      .get('input')
      .type('My Second Todo')
      .get('.add-todo')
      .click()
      .get('.todo-list')
      .eq(0)
      .click()

      // assertions on view, tabs and redux
      .get('.filter')
      .eq(0)
      .should('include.text', 'all')
      .click()
      .get('.todo-item')
      .should('include.text', '👋 My First Todo')

      .get('.filter')
      .eq(2)
      .should('include.text', 'incomplete')
      .click()
      .get('.todo-item')
      .should('include.text', '👋 My First Todo')

      .get('.filter')
      .eq(1)
      .should('include.text', 'completed')
      .click()
      .get('.todo-item')
      .should('have.text', '👌 My Second Todo')
      .eq(0)
      .children()
      .should('have.class', 'todo-item__text--completed')
      .get('.filter')
      .eq(0)
      .should('include.text', 'all')
      .click()

      .window()
      .its('store')
      .invoke('getState')
      .then((state) => {
        expect(state.visibilityFilter).to.be.a('string').and.equal('all')
        expect(state.todos.allIds).to.be.a('array').and.to.have.lengthOf(2)
        expect(state.todos.byIds)
          .to.be.a('object')
          .and.to.deep.equal({
            1: { content: 'My First Todo', completed: false },
            2: { content: 'My Second Todo', completed: true },
          })
      })
  })
})

Watch it Run!

cypress successful test running
All green!!

Want more ?

We can also dispatch redux actions like this.

cy.window()
  .its('store')
  .invoke('dispatch', {
    type: 'ADD_TODO',
    payload: { content: 'Dispatched TODO', completed: true },
  })