styler
styler

Reputation: 16511

Jest Expected mock function to have been called, but it was not called

I've looked at various suggestions to solve testing a class property with no success and was wondering if anyone could possibly cast a little more light on where I may be going wrong, here are the tests I've tried all with the error Expected mock function to have been called, but it was not called.

Search.jsx

import React, { Component } from 'react'
import { func } from 'prop-types'
import Input from './Input'
import Button from './Button'

class SearchForm extends Component {
  static propTypes = {
    toggleAlert: func.isRequired
  }

  constructor() {
    super()

    this.state = {
      searchTerm: ''
    }

    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleSubmit = () => {
    const { searchTerm } = this.state
    const { toggleAlert } = this.props

    if (searchTerm === 'mocky') {
      toggleAlert({
        alertType: 'success',
        alertMessage: 'Success!!!'
      })

      this.setState({
        searchTerm: ''
      })
    } else {
      toggleAlert({
        alertType: 'error',
        alertMessage: 'Error!!!'
      })
    }
  }

  handleChange = ({ target: { value } }) => {
    this.setState({
      searchTerm: value
    })
  }

  render() {
    const { searchTerm } = this.state
    const btnDisabled = (searchTerm.length === 0) === true

    return (
      <div className="well search-form soft push--bottom">
        <ul className="form-fields list-inline">
          <li className="flush">
            <Input
              id="search"
              name="search"
              type="text"
              placeholder="Enter a search term..."
              className="text-input"
              value={searchTerm}
              onChange={this.handleChange}
            />
            <div className="feedback push-half--right" />
          </li>
          <li className="push-half--left">
            <Button className="btn btn--positive" disabled={btnDisabled} onClick={this.handleSubmit}>
              Search
            </Button>
          </li>
        </ul>
      </div>
    )
  }
}

export default SearchForm

First option:

it('should call handleSubmit function on submit', () => {
    const wrapper = shallow(<Search toggleAlert={jest.fn()} />)
    const spy = jest.spyOn(wrapper.instance(), 'handleSubmit')
    wrapper.instance().forceUpdate()
    wrapper.find('.btn').simulate('click')
    expect(spy).toHaveBeenCalled()
    spy.mockClear()
  })

Second option:

it('should call handleSubmit function on submit', () => {
    const wrapper = shallow(<Search toggleAlert={jest.fn()} />)
    wrapper.instance().handleSubmit = jest.fn()
    wrapper.update()
    wrapper.find('.btn').simulate('click')
    expect(wrapper.instance().handleSubmit).toHaveBeenCalled()
  })

I get that with a class property the function is an instance of the class requiring the component to be updated in order to register the function, it looks however like the component handleSubmit function gets called instead of the mock?

Swapping out handleSubmit to be a class function as a method gives me access on the class prototype which passes the test when spying on Search.prototype but I'd really like to get a solution to the class property approach.

All suggestions and recommendations would be grateful!

Upvotes: 22

Views: 115505

Answers (5)

Kuldeep Bhimte
Kuldeep Bhimte

Reputation: 959

I suppose the unit test should be robust enough to catch the error, if case of any undesirable code changes.

Please include strict assertions in your tests.

For the conditional statements, please cover the branches as well. E.g in case of if and else statement you will have to write two tests.

For user actions, you should try to simulate the actions rather than calling the function manually.

Please see the example below,

import React from 'react';
import { shallow } from 'enzyme';
import { SearchForm } from 'components/Search';


describe('Search Component', () => {
  let wrapper;
  const toggleAlert = jest.fn();
  const handleChange = jest.fn();
  const successAlert = {
    alertType: 'success',
    alertMessage: 'Success!!!'
  }
  const errorAlert = {
    alertType: 'error',
    alertMessage: 'Error!!!'
  }
  beforeEach(() => {
    wrapper = shallow(<SearchForm toggleAlert={toggleAlert} />);
  });
  it('"handleSubmit" to have been called with "mocky"', () => {
    expect(toggleAlert).not.toHaveBeenCalled();
    expect(handleChange).not.toHaveBeenCalled();
    wrapper.find('Input').simulate('change', { target: { value: 'mocky' } });
    expect(handleChange).toHaveBeenCalledTimes(1);
    expect(wrapper.state().searchTerm).toBe('mocky');
    wrapper.find('Button').simulate('click');
    expect(toggleAlert).toHaveBeenCalledTimes(1);
    expect(toggleAlert).toHaveBeenCalledWith(successAlert);
    expect(wrapper.state().searchTerm).toBe('');
  });

  it('"handleSubmit" to have been called with "other than mocky"', () => {
    expect(toggleAlert).not.toHaveBeenCalled();
    expect(handleChange).not.toHaveBeenCalled();
    wrapper.find('Input').simulate('change', { target: { value: 'Hello' } });
    expect(handleChange).toHaveBeenCalledTimes(1);
    expect(wrapper.state().searchTerm).toBe('Hello');
    wrapper.find('Button').simulate('click');
    expect(toggleAlert).toHaveBeenCalledTimes(1);
    expect(toggleAlert).toHaveBeenCalledWith(errorAlert);
    expect(wrapper.state().searchTerm).toBe('Hello');
  });
});

Upvotes: 14

user13069482
user13069482

Reputation: 41

A solution that could work is;

Mock the function before shallow:

let handleSubmitMock = jest.fn();
LoginPage.prototype.handleSubmit = function() {  handleSubmitMock() };

Use this to expect:

form.props.onSubmit();
expect(handleSubmitMock).toHaveBeenCalledTimes(1);

Upvotes: 4

trevorgk
trevorgk

Reputation: 1466

You shouldn't need to write unit tests for this scenario. You should be able to trust that the framework will fire the correct handlers that you've provided. A more useful test would be one which mocks the toggleAlert prop and tests the instance method handleSubmit. This is where the majority of custom logic will reside and consequently where we are most likely to find errors. Snapshot testing should be fine for anything that is part of the render function output.

A sensible test suite for this component would resemble something like the following:

describe('handleSubmit', () => {
  let wrapper;
  let spy;

  describe('when searchTerm is "mocky"', () => {
    beforeEach(() => {
      spy = jest.fn();
      wrapper = shallow(<SearchForm toggleAlert={spy} />);
      wrapper.setState({ searchTerm: 'mocky' });
    });

    it('will fire spy with expected arguments', () => {
      // verify that spy has not been fired prior to test
      expect(spy).not.toBeCalled();

      wrapper.instance().handleSubmit();

      expect(spy).toBeCalled();
      expect(spy).toBeCalledWith({
        alertType: 'success',
        alertMessage: 'Success!!!'
      });
    });

    it('will set searchTerm to ""', () => {
      expect(wrapper.state('searchTerm')).toBe('mocky');
      wrapper.instance().handleSubmit();
      expect(wrapper.state('searchTerm')).toBe('');
    });
  });

  describe('when searchTerm is "something else"', () => {
    beforeEach(() => {
      spy = jest.fn();
      wrapper = shallow(<SearchForm toggleAlert={spy} />);
      wrapper.setState({ searchTerm: 'something else' });
    });

    it('will fire spy with expected arguments', () => {
      // verify that spy has not been fired prior to test
      expect(spy).not.toBeCalled();

      wrapper.instance().handleSubmit();

      expect(spy).toBeCalled();
      expect(spy).toBeCalledWith({
        alertType: 'error',
        alertMessage: 'Error!!!'
      });
    });
  });
});

Upvotes: 3

styler
styler

Reputation: 16511

So I've managed to create a working solution by first of all updating the wrapper instance and then updating the wrapper. Test now works.

Working test looks like:

it('should call handleSubmit function on submit', () => {
    const wrapper = shallow(<Search toggleAlert={jest.fn()} />)
    wrapper.instance().handleSubmit = jest.fn()
    wrapper.instance().forceUpdate()
    wrapper.update()
    wrapper.find('.btn').simulate('click')
    expect(wrapper.instance().handleSubmit).toHaveBeenCalled()
  })

Upvotes: 5

Try something like this

it('should call handleSubmit function on submit', () => {
        const toggleAlert = jest.fn();
        const wrapper = shallow(<Search toggleAlert={toggleAlert} />)
        wrapper.setState({ searchText: 'mocky' });
        wrapper.find('Button').at(0).simulate('click');
        expect(toggleAlert).toHaveBeenLastCalledWith({
                   alertType: 'success',
                   alertMessage: 'Success!!!'
              });
      })

****Update

 constructor(props) {
    super(props) //you have to add props to access it this.props

    this.state = {
      searchTerm: ''
    }

    this.handleSubmit = this.handleSubmit.bind(this)
  }

Upvotes: 3

Related Questions