Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spying on React functional component method with jest and enzyme; Cannot spyOn on a primitive value

I am trying to test a React component and make sure that when its button gets clicked, the correct method gets invoked. However, when I try to run my test and try to spy on that method, I get the following message:

Error: Cannot spyOn on a primitive value; undefined given

How do I test that when a button is clicked the correct method is invoked? Thanks!

sampleComponent.jsx:

import * as React from 'react';

const SampleComponent = () => {
  const sampleMethod = () => {
    console.log('hello world');
  };

  return <button onClick={sampleMethod} type="button">Click Me</button>;
};

export default SampleComponent;

sampleComponent.test.jsx:

import * as React from 'react';
import { shallow } from 'enzyme';
import SampleComponent from './sample';

test('testing spy', () => {
  const spy = jest.spyOn(SampleComponent.prototype, 'sampleMethod');
  const wrapper = shallow(<SampleComponent />);
  wrapper.find('button').simulate('click');
  expect(spy).toHaveBeenCalled();
});
like image 550
Jimmy Avatar asked Oct 28 '19 22:10

Jimmy


3 Answers

The error means, the function sampleMethod you defined inside the functional component SampleComponent is not defined in SampleComponent.prototype. So SampleComponent.prototype.sampleMethod is undefined, jest can't spy on a undefined value.

So the correct way to test sampleMethod event handler is like this:

index.spec.tsx:

import React from 'react';
import SampleComponent from './';
import { shallow } from 'enzyme';

describe('SampleComponent', () => {
  test('should handle click correctly', () => {
    const logSpy = jest.spyOn(console, 'log');
    const wrapper = shallow(<SampleComponent></SampleComponent>);
    const button = wrapper.find('button');
    expect(button.text()).toBe('Click Me');
    button.simulate('click');
    expect(logSpy).toBeCalledWith('hello world');
  });
});

We can spy on console.log, to assert it is to be called or not.

Unit test result with 100% coverage:

 PASS  src/react-enzyme-examples/02-react-hooks/index.spec.tsx
  SampleComponent
    ✓ should handle click correctly (19ms)

  console.log node_modules/jest-mock/build/index.js:860
    hello world

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 index.tsx |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.036s

Dependencies version:

"react": "^16.11.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"jest": "^24.9.0",
"jest-environment-enzyme": "^7.1.1",
"jest-enzyme": "^7.1.1",
like image 79
slideshowp2 Avatar answered Nov 04 '22 01:11

slideshowp2


sample.js

import * as React from 'react';

export let util = {sampleMethod: null };

const SampleComponent = () => {
  util.sampleMethod = () => {
    console.log('hello world');
  };

  return <button onClick={sampleMethod} type="button">Click Me</button>;
};

export default SampleComponent;

sample.test.js

import { shallow } from 'enzyme';
import SampleComponent, {util} from './sample';

test('testing spy', () => {
  const spy = jest.spyOn( util, 'sampleMethod' );
  const wrapper = shallow(<SampleComponent />);
  wrapper.find('button').simulate('click');
  expect(spy).toHaveBeenCalled(1);
});

I know I'm late to answer but I think this would help some other developers also

like image 8
Murtaza Huzaifa Avatar answered Nov 04 '22 03:11

Murtaza Huzaifa


Also searching for a way on spying on a function inside a functional component, it seems just not possible to be done nicley (booo!). I didn't want to spy on a console log, the 2nd suggestion using an object defined outside the fc I was not able to get it working.

I came up with a solution which is also not nice, but simple and may help others with this problem. It is NOT spying on the function, which was asked for, but the outcome is may be close enough for some scenarios.

MyFC.tsx

const MyFC = ({callback}: {callback?:()=>void}) => {
    const handleMyClick = 
        callback // this is the mock fn
        || ()=> console.log("do stuff") // this would be the regular implementation

    return <button onClick={handleMyClick}>Click Me</button>
}

MyFC.test.tsx

it('should be triggered', () => {
    const mockFn = jest.fn();
    const wrapper = mount(<MyFC callback={mockFn}/>);
    wrapper.find('button').simulate('click')
    expect(mockFn).toBeCalled()        
}

with this approach you can at least test if it's called, with what arguments etc If somebody finds a way to do this properly, I would be glad to hear...

like image 1
o-faro Avatar answered Nov 04 '22 02:11

o-faro