Search code examples
javascriptreactjsjestjsstyled-components

sinon spy testing onClick react component with styled-components


I am using to sinon to test a react component to confirm that a function is triggered onClick.

I am using styled-components so finding it difficult to target the element to click on.

I am seeing the following error:

Method “simulate” is only meant to be run on a single node. 0 found instead.

My react code looks like this:

import React, { Component } from 'react'
import styled from 'styled-components'
import PropTypes from 'prop-types'

class Button extends Component {

  pressNumber = () => {
    this.props.updateNumber(this.props.number)
  }

  render() {

    return (
      <ButtonContainer 
        onClick={ this.pressNumber }
      >
        <Number>{ this.props.number }</Number>
      </ButtonContainer>
    )
  }

}

const ButtonContainer = styled.div`
  height: 60px;
  width: 60px;
`

const Number = styled.div`
  color: #fff;
  font-size: 26px;
  font-weight: 300;
`

My test then looks like this:

import { expect } from 'chai'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import React from 'react'
import Button from './index'

describe('Button', () => {

  let wrapper
  const pressNumber = () => {}

  beforeEach(() => {
    wrapper = mount(
      <Button 
        number={1} 
        pressNumber={ pressNumber } 
      />
    )
  })

  it('should call the update pin prop when click is simulated', () => {
    const updatePinClick = sinon.spy();
    wrapper.find('ButtonContainer').simulate('click')
    expect(updatePinClick.calledOnce).to.be.true
  })

})

Can anyone see what I am doing wrong here, and if there is a different approach as I am using styled-components.

I am seeing the following error

Method “simulate” is only meant to be run on a single node. 0 found instead.


Solution

  • (disclosure: i'm an enzyme maintainer)

    Your spy is updatePinClick, but you're not passing it anywhere, so it can't be used. Additionally, Button doesn't take a pressNumber prop.

    First, I'd suggest the following general tips:

    1. Avoid .simulate - if you want to invoke a prop, do it directly, like .prop('onClick')().
    2. Don't define the wrapper in a beforeEach - in tests, it's far better to repeat yourself, and specifically the jsx you're using.
    3. Avoid passing strings into .find; it's less brittle to export the component you want to find, and find it by reference instead.
    4. Avoid noop matchers - ie, things that don't end in a function, like .to.be.true. This is because you could easily make a typo and it would fail silently - expect(object).to.be.yogurt; will happily pass, even though that's not a valid matcher.
    5. Prefer shallow for all your tests. Only use mount when it's necessary to use refs, or to test componentDidMount, or other browser-only code.

    Specifically, try this:

    import { expect } from 'chai'
    import { shallow, mount } from 'enzyme'
    import sinon from 'sinon'
    import React from 'react'
    import Button from './index'
    
    describe('Button', () => {
      it('should call the update pin prop when click is simulated', () => {
        const updatePinClick = sinon.spy();
        const wrapper = shallow(
          <Button 
            number={1} 
            updateNumber={updatePinClick} 
          />
        );
        wrapper.find('ButtonContainer').prop('onClick')();
        expect(updatePinClick).to.have.property('callCount', 1);
        expect(updatePinClick.calledWith(1)).to.equal(true);
      });
    })