[React & Testing] Simulate Event testing

Here we want to test a toggle button component, when the button was click, state should change, style should change also.

Toggle component:

// see this live: https://codesandbox.io/s/GvWpGjKQ
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import glamorous from 'glamorous'
import {darken} from 'polished'

// imagine this is in a "components" file
const primaryColor = '#337ab7'
const toggledOnStyles = {
  backgroundColor: darken(0.15, primaryColor),
  borderColor: darken(0.25, primaryColor),
  '&:hover,&:active,&:focus': {
    backgroundColor: darken(0.2, primaryColor),
    borderColor: darken(0.3, primaryColor),
  },
}
const toggledOffStyles = {
  backgroundColor: primaryColor,
  borderColor: darken(0.1, primaryColor),
  '&:hover,&:active,&:focus': {
    backgroundColor: darken(0.1, primaryColor),
    borderColor: darken(0.2, primaryColor),
  },
}
const ToggleButton = glamorous.button(
  {
    display: 'inline-block',
    padding: '6px 12px',
    marginBottom: '0',
    fontSize: '14px',
    fontWeight: '400',
    lineHeight: '1.4',
    textAlign: 'center',
    cursor: 'pointer',
    borderRadius: '4px',
    color: '#fff',
  },
  props => (props.on ? toggledOnStyles : toggledOffStyles),
)

class Toggle extends Component {
  constructor(props, ...rest) {
    super(props, ...rest)
    this.state = {
      toggledOn: props.initialToggledOn || false,
    }
  }

  handleToggleClick = () => {
    const toggledOn = !this.state.toggledOn
    this.props.onToggle(toggledOn)
    this.setState({toggledOn})
  }

  render() {
    const {children} = this.props
    const {toggledOn} = this.state
    return (
      <ToggleButton
        on={toggledOn}
        onClick={this.handleToggleClick}
        data-test="button"
      >
        {children}
      </ToggleButton>
    )
  }
}

Toggle.propTypes = {
  initialToggledOn: PropTypes.bool,
  onToggle: PropTypes.func.isRequired,
  children: PropTypes.any.isRequired,
}

export default Toggle

Test:

import React from 'react'
import {render, mount} from 'enzyme'
import Toggle from '../toggle'

test('component render with default state', () => {
    const wrapper = renderToggle();
    expect(wrapper).toMatchSnapshotWithGlamor();
})

test('when button is clicked, the style of button should change', () => {
    const onToggle = jest.fn() // jest mock function
    const wrapper = mountToggle({
        onToggle
    })
    // It is recommended that for the element we need to test
    // we can add 'data-test' attr, so that we can reference
    // the element inside testing
    const button = wrapper.find('[data-test="button"]')
    // we can verify the style changes inside snapshots
    expect(wrapper).toMatchSnapshotWithGlamor('1. Before toggle')
    button.simulate('click')
    expect(wrapper).toMatchSnapshotWithGlamor('2. After toggle')
})

test('onToggle function should be called when the button is clicked', () => {
    const onToggle = jest.fn() // jest mock function
    const wrapper = mountToggle({
        onToggle
    })
    // It is recommended that for the element we need to test
    // we can add 'data-test' attr, so that we can reference
    // the element inside testing
    const button = wrapper.find('[data-test="button"]')
    button.simulate('click')
    expect(onToggle).toHaveBeenCalledTimes(1)
    expect(onToggle).toHaveBeenCalledWith(true)
})

/**
 * The difference between mount and render function is that
 * 1. render is faster, because after rendered, it output string,
 * so there is no lifecycle hooks bind with it.
 * 2. mount, on the other hand, will bind lifecycle hooks and events,
 * the output is actual DOM element
 * */

function mountToggle(props = {}) {
    const propToUse = Object.assign(
        {},
        {
            onToggle() {
            },
            children: 'I am a child'
        },
        props
    )

    return mount(<Toggle {...propToUse} />)
}

function renderToggle(props = {}) {
    const propToUse = Object.assign(
        {},
        {
            onToggle() {
            },
            children: 'I am a child'
        },
        props
    )

    return render(<Toggle {...propToUse} />)
}