6

I'm using react with redux and testing with cypress, I was able to access the store using cy.window().its('store').invoke('getState').then((state) => {} But how do i access a component's local state rather than the application store?

I tried

cy.get('.simple-component').its('getState')

or

cy.get('.simple-component').invoke('getState')

but Cypress is returning "CypressError: Timed out retrying: cy.invoke() errored because the property: 'getState' does not exist on your subject" And on the Cypress console (in chrome) it's yeilding:

Yielded:   
<div class="simple-component" getstate="[object Object]"></div>

It seems that's caused by React removing the methods from the DOM so i need to access it in React rather than in the DOM?

import React, { Component } from 'react';

class simpleComponent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            sample: "hello"
            }
    }
    // getState() just for testing on cypress
    getState() {
      return this.state
    }
    render(){
      return <div className="simple-component" getState={this.getState()}></div>
    }    
}

As an alternative can i export the local component state at the end of the simple-component using window.store?

Omar
  • 116
  • 1
  • 3
  • 10
  • 2
    With an end to end testing tool like Cypress you wouldn’t actually be interacting with the React API or directly with the internals of a component such as executing setState(). Instead you be validating/testing what the user would see and experience. What you are attempting to do is more alone the lines of unit testing with tools like Jest/Enzyme. With unit testing you could invoke the internal actions of components such as event handlers and validate Redux store data before/after various events or API calls. Cypress really isn’t meant for that type of testing. – Alexander Staroselsky Mar 26 '19 at 03:53
  • Also your components getState prop is executing this.getState() on every render rather than binding the class function to be passed to a child component or execute in response to an event such as click. What’s the intention of that prop and class method in terms of your React application? You won’t really be able to expose state to window or for external use like that. – Alexander Staroselsky Mar 26 '19 at 04:01
  • Good point, it's not in the original code but i added it to see it on the cypress console to eliminate the chance that Cypress isn't seeing it in the
    Otherwise your first point answers my question, thought it would be nice to be able to access local state similarly to the redux store using Cypress
    – Omar Mar 26 '19 at 04:13
  • This one has exactly what you asked for. https://stackoverflow.com/a/39165137 – Simon Landeholm Mar 03 '20 at 11:26

2 Answers2

8

>= version 7.0.0

As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress

From https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react:

We still need to install the react adapter to mount components:

yarn add -D cypress @cypress/react @cypress/webpack-dev-server

add a glob pattern matching your component tests to cypress.json:

{
  "component": {
    "testFiles": "**/*.test.{js,ts,jsx,tsx}",
    "componentFolder": "src"
  }
}

Tell Cypress to use @cypress/webpack-dev-server for component tests. in cypress/plugins/index.js:

const injectDevServer = require("@cypress/react/plugins/react-scripts")

module.exports = (on, config) => {
  injectDevServer(on, config)
  return config
}

This will configure the Cypress Webpack Dev Server to use the same Webpack configuration as Create React App uses.

If you are using a different template, like Next.js, we have some other adapters available. It's also possible to create your own adapter.

< version 7.0.0

There's a Cypress Plugin for that, called react-unit-test. It gives you the ability to mount React components directly (adds a cy.mount() command) and provides access to the component's internal state.

Here's an example from the repo's readme:

// load Cypress TypeScript definitions for IntelliSense
/// <reference types="cypress" />
// import the component you want to test
import { HelloState } from '../../src/hello-x.jsx'
import React from 'react'
describe('HelloState component', () => {
  it('works', () => {
    // mount the component under test
    cy.mount(<HelloState />)
    // start testing!
    cy.contains('Hello Spider-man!')
    // mounted component can be selected via its name, function, or JSX
    // e.g. '@HelloState', HelloState, or <HelloState />
    cy.get(HelloState)
      .invoke('setState', { name: 'React' })
    cy.get(HelloState)
      .its('state')
      .should('deep.equal', { name: 'React' })
    // check if GUI has rerendered
    cy.contains('Hello React!')
  })
})
kuceb
  • 16,573
  • 7
  • 42
  • 56
2

You can identify the element without mounting the react component. If you are testing your react app in isolation with the source code or writing functional UI test cases, you can consider a Cypress plugin called cypress-react-selector. It helps you identify web elements by component, props, and state even after the minification. You need to use React Dev Tool to identify the component names in that case.

Here is an example:

Suppose your react app:

const MyComponent = ({ someBooleanProp }) => (
  <div>My Component {someBooleanProp ? 'show this' : ''} </div>
)
 
const App = () => (
  <div id='root'>
    <MyComponent />
    <MyComponent name={bob} />
  </div>
)
 
ReactDOM.render(<App />, document.getElementById('root'))

Then you can simply identify the react element like:

cy.getReact('MyComponent', { name: 'bob' } ).getCurrentState();

Find more sample test here

Hope it will help!

Francis Upton IV
  • 19,322
  • 3
  • 53
  • 57
Abhinaba
  • 376
  • 1
  • 8