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
.
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(
<Provider store={store}>
<TodoApp />
</Provider>,
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
/// <reference types="Cypress" />
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 },
})
})
})
})
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 },
})
Top comments (0)