61

I recently wanted to test that some custom method gets conditionally called in the componentDidMount method of a React component.

componentDidMount() {
  if (this.props.initOpen) {
    this.methodName();
  }
}

I'm using Jest as my testing framework, which includes jest.fn() for mocks/spies. I've read that this would be fairly trivial to test with Sinon, by doing something like the following:

sinon.spy(Component.prototype, "methodName");
const wrapper = mount(<Component {...props} />);
expect(wrapper.instance().methodName).toHaveBeenCalled();

I'm trying to recreate this with Jest like so:

Component.prototype.methodName = jest.fn();
const wrapper = mount(<Component {...props} />);
expect(wrapper.instance().methodName).toHaveBeenCalled();

This code fails and throws the following error:

jest.fn() value must be a mock function or spy.
Received:
  function: [Function bound mockConstructor]

Is it possible to test this functionality with Jest? And if so, how?

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
seansean11
  • 949
  • 2
  • 9
  • 16

4 Answers4

107

The key is using jests spyOn method on the object's prototype. It should be like this:

const spy = jest.spyOn(Component.prototype, 'methodName');
const wrapper = mount(<Component {...props} />);
wrapper.instance().methodName();
expect(spy).toHaveBeenCalled();

As found here e.g.: Test if function is called react and enzyme

Please note it is also best practice to clear the spied function after each test run

let spy

afterEach(() => {
  spy.mockClear()
})

https://facebook.github.io/jest/docs/en/jest-object.html#jestclearallmocks

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Jonathan
  • 1,685
  • 2
  • 18
  • 25
  • Thank you! Looks like that just came out in 19.0.0, just a couple months ago. Can't believe I skipped it over in documentation. – seansean11 Apr 08 '17 at 14:32
  • Will it actually call the `methodName()` function in the Component or it's just faking it ? – prime Sep 12 '17 at 17:12
  • From the documentation: "Note: By default, jest.spyOn also calls the spied method." https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname – Jonathan Sep 12 '17 at 19:43
  • 2
    What if the `` wrapped around with a ``, will this work then ? I'm getting a `Cannot read property '_isMockFunction' of undefined'`. Not sure it's because of the above reason though. – prime Sep 27 '17 at 08:55
  • 1
    To me it seems like you are spying on a method that does not exist and that is why you get the `Cannot read property '_isMockFunction' of undefined'`. – Jonathan Sep 27 '17 at 09:04
  • I have the same example with the component wrapped inside a provider and I get the same behavior as @prime. If I try to log the prototype of the component before spying on its members and mounting it it seems to be an empty object. – Vee6 Nov 15 '17 at 14:02
  • @prime Hi, do you know maybe after that time how to work with that? I have same isue now. – Artimal Nov 29 '17 at 09:24
  • @Vee6 Do you know now how to solve that problem? I am using wrapper = mount(); and I get same error. – Artimal Nov 29 '17 at 09:25
  • @Edenwave I think I did not. anyway what is your requirement ? – prime Nov 29 '17 at 09:38
  • @prime I am describing it here https://stackoverflow.com/questions/47532213/jestenzyme-cannot-read-property-ismockfunction-of-undefined-simulate-keydo – Artimal Nov 29 '17 at 09:38
  • @Edenwave no luck, I just refactored my code so that it is testable without using that kind of assertion. – Vee6 Nov 29 '17 at 13:02
  • In case you have other tests you may want to do the following one liner check in your afterEach: ```spy && spy.mockClear && spy.mockClear()``` – Jason Rice Nov 15 '19 at 20:41
  • Thanks! I see the order of spy declaration is very important. It has to be made before object construction! – Marecky Jun 21 '22 at 16:27
23

I know its a bit late, but I came across this and would suggest that to test componentDidMount initiates the call to your nested method that your test should look something like:

Module

componentDidMount() {
  if (this.props.initOpen) {
    this.methodName();
  }
}

Test - Good

it('should call methodName during componentDidMount', () => {
    const methodNameFake = jest.spyOn(MyComponent.prototype, 'methodName');
    const wrapper = mount(<MyComponent {...props} />);
    expect(methodNameFake).toHaveBeenCalledTimes(1);
});

If you call componentDidMount then the assertion that methodName was called via componentDidMount is more valid.

Test - Bad

it('should call methodName during componentDidMount', () => {
    const spy = jest.spyOn(Component.prototype, 'methodName');
    const wrapper = mount(<Component {...props} />);
    wrapper.instance().methodName();
    expect(spy).toHaveBeenCalled();
}

By writing the test like this - you call the method and then assert that it was called. Which of course it will have given you just called it.

Seth McClaine
  • 9,142
  • 6
  • 38
  • 64
hloughrey
  • 958
  • 3
  • 11
  • 22
0

If you're trying to test public methods being called on componentDidMount (if you're using TypeScript), you'll need to explicitly call the instance's componentDidMount method call, since the public methods aren't defined until after the component is instantiated.

To test something like this:

Code

public componentDidMount() {
  if (this.props.initOpen) {
    this.methodName();
  }
}

public methodName = () => {
  // some code here
}

Test

it('should call methodName during componentDidMount', () => {
  const wrapper = mount(<MyComponent {...props} />);
  const instance = wrapper.instance();
  jest.spyOn(instance, 'methodName')
  expect(instance.methodName).toHaveBeenCalled();
});
TheScrappyDev
  • 4,375
  • 2
  • 21
  • 25
0
const toastMethodSpy = jest.spyOn(sharedMockedOTPComponent, 'toast')
sharedMockedOTPComponent.handleResendOtpFailure(networkError)

//hide loader
expect(sharedMockedOTPComponent.state.showLoader).toBe(false)
//error message in toast should have been shown
expect(toastMethodSpy).toHaveBeenCalledTimes(1)
Harish Gyanani
  • 1,366
  • 2
  • 22
  • 43