Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When spying on component method, called from componentDidMount, the spy is never called

In a React component I have

export default class MyComp extends Component {
  ...
  componentDidMount() {
    this.customFunc();
  }
  customFunc = () => {
    // ..
  }
  ...
}

And when I try to test this method with Jest and Enzyme like this:

it('Should call customFunc on mount', () => {
  const MyCompInstance = mount(<MyComp {...props} >).instance();
  const spy = jest.spyOn(MyCompInstance, 'customFunc');

  expect(spy).toHaveBeenCalled();
});

it fails with Expected mock function to have been called, but it was not called.

The funny thing is that if I put console.log() in componentDidMount and in custsomFunc - they get called. What am I doing wrong?

PS: I tried with forceUpdate on the instance, right before the expect, but I still get the same error.

like image 593
pesho hristov Avatar asked Dec 05 '22 10:12

pesho hristov


1 Answers

it fails with Expected mock function to have been called, but it was not called.

The funny thing is that if I put console.log() in componentDidMount and in custsomFunc - they get called.

Calling mount renders the component and componentDidMount gets called as part of that process, which in turn calls customFunc.

The spy then gets created on customFunc but by that time it is too late since componentDidMount and customFunc have already run as part of mount and the test fails with the spy reporting that it was not called.


What am I doing wrong?

You need to create the spy on customFunc before it gets called.

This is very difficult the way the code is currently written since customFunc is implemented as an instance property.

Because it is an instance property it won't exist until the instance exists but the instance gets created during the rendering process which ends up calling componentDidMount.

In other words, you need an instance to spy on customFunc, but customFunc gets called as part of creating the instance.

In this scenario the only way to check if customFunc is called when componentDidMount runs is to call it again after the instance has been created and the component has rendered, which is kind of a hack:

import * as React from 'react';
import { mount } from 'enzyme';

class MyComp extends React.Component {
  componentDidMount() {
    this.customFunc();
  }
  customFunc = () => { }  // instance property
  render() { return <div/> }
}

it('Should call customFunc on mount', () => {
  const instance  = mount(<MyComp />).instance();
  const spy = jest.spyOn(instance, 'customFunc');  // spy on customFunc using the instance
  instance.componentDidMount();  // call componentDidMount again...kind of a hack
  expect(spy).toHaveBeenCalled();  // SUCCESS
});

The alternative is to implement customFunc as a class method.

If customFunc is a class method then it will exist on the prototype of the class which allows you to create a spy on customFunc before the instance gets created during the render process in mount:

import * as React from 'react';
import { mount } from 'enzyme';

class MyComp extends React.Component {
  componentDidMount() {
    this.customFunc();
  }
  customFunc() { }  // class method
  render() { return <div/> }
}

it('Should call customFunc on mount', () => {
  const spy = jest.spyOn(MyComp.prototype, 'customFunc');  // spy on customFunc using the prototype
  const wrapper = mount(<MyComp />);
  expect(spy).toHaveBeenCalled();  // SUCCESS
});
like image 103
Brian Adams Avatar answered Feb 14 '23 18:02

Brian Adams