0

At first, I thought there was something wrong with other aspects of my code. So I created a new, simplified version of the component in a newly created project and wrote the test for it but my spy is still not being called.

Here's the component I'm testing:

import React from 'react';

class TextEditor extends React.Component {
  handleChange = (e) => {
    console.log({ value: e.target.value });
  }

  render() {
    return (
      <div>
        <input type="text" name="name" id="name" onChange={this.handleChange} />
      </div>
    );
  }
}

export default TextEditor;

And here is the unit test:

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

describe('TextEditor', () => {
  it('handles change event', () => {
    const wrapper = shallow(<TextEditor />);
    const spy = jest.spyOn(wrapper.instance(), 'handleChange');
    wrapper.find('input').simulate('change', { target: { value: 'test value' }});
    expect(spy).toHaveBeenCalledTimes(1);
  });
});

The result of running the test: enter image description here

When I run this, it fails because the spy doesn't get called. But notice that the console.log statement in the handleChange function gets executed. So the test actually calls the function but the spy isn't recognized as having been called.

What could I be doing wrong? Thanks for your ideas.

Awa Melvine
  • 3,797
  • 8
  • 34
  • 47
  • `handleChange` isn't a property of the instance, it's a function nested inside another function. It's inaccessible from outside. Even if it _was_ accessible, your test would be of _implementation details_. Test the _behaviour_ instead - does the right value get logged? – jonrsharpe Jan 10 '22 at 16:14
  • Hi @jonrsharpe, thanks for your comment. I think your comment is predicated on the assumption that the component being tested is a functional component but it is actually a class component, so the method _does_ belong to the instance; or did I misunderstand you? – Awa Melvine Jan 10 '22 at 16:30
  • 1
    You're right, it is; sorry, I misread that. The point stands, though: test behaviour not implementation. – jonrsharpe Jan 10 '22 at 16:31

1 Answers1

1

The handleChange method is class properties, not the instance method of the class.

If you insist to use class properties, you should call wrapper.instance().forceUpdate() after spying. See issue#365

E.g.

TextEditor.tsx:

import React from 'react';

class TextEditor extends React.Component {
  handleChange = (e) => {
    console.log({ value: e.target.value });
  };

  render() {
    return (
      <div>
        <input type="text" name="name" id="name" onChange={this.handleChange} />
      </div>
    );
  }
}

export default TextEditor;

TextEditor.test.tsx:

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

describe('TextEditor', () => {
  it('handles change event', () => {
    const wrapper = shallow(<TextEditor />);
    const spy = jest.spyOn(wrapper.instance(), 'handleChange');
    wrapper.instance().forceUpdate();
    wrapper.find('input').simulate('change', { target: { value: 'test value' } });
    expect(spy).toHaveBeenCalledTimes(1);
  });
});

Test result:

 PASS  examples/70652888/TextEditor.test.tsx (13.006 s)
  TextEditor
    ✓ handles change event (100 ms)

  console.log
    { value: 'test value' }

      at TextEditor.handleChange (examples/70652888/TextEditor.tsx:5:13)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        14.708 s

Also, see this answer

Lin Du
  • 88,126
  • 95
  • 281
  • 483