Mock function not being called with react-testing-library, but original function is called

Viewed 621

I have, I think, a simple case. I am using a function in my component that is imported from a different file. I've mocked the function as shown below. I've also tried adding a mock implementation, but that didn't work either. If I put a print statement in my original function, that appears when I run the test. If I understand correctly, a mock should replace the original.

I've tried to make this as straightforward as possible, so I'm not sure what I'm missing.

test file:

import AddStoryModal from './AddStoryModal';
import addStory from '../api/add_story';

jest.mock('../api/add_story');

describe('Story tests', () => {

  it('makes an API call to add a story', () => {
    render(<AddStoryModal />);

    const submitButton = screen.getByTestId('add-submit');  
    fireEvent.click(submitButton);
    expect(addStory).toHaveBeenCalled();
  });
});

result:

 expect(jest.fn()).toBeCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

component being tested:

...
import addStory from '../api/add_story';

function AddStoryModal(){

  const onFinish = (values) => {
    addStory();
  };

  return (
    <>
      <Button type="primary" data-testid="add-story" onClick={showModal}>
        Create New +
      </Button>

      <Modal title="Add New Story ..." visible={visible} data-testid='add-story-modal' onCancel={handleCancel} footer={null}>
        <Form form={form} layout="vertical" onFinish={onFinish}>
          ...
          <Form.Item>
            <Button type="primary" data-testid="add-submit" htmlType="submit">
              Submit
            </Button>
          </Form.Item>
        </Form>
      </Modal>
    </>
  );
}
1 Answers

This is a nice example of how writing tests can make your production code better. Yes, you could continue with using jest.mock(*module*) and work out what's going wrong, but why?

Have a look at what AddStoryModal is actually doing. It's very coupled to calling the API function, which in turn is what is making testing awkward.

If you pass the onFinish implementation function as a prop you immediately get some great wins:

  • Less code in AddStoryModal
  • It becomes more versatile (and increases the chances of being reused)
  • It becomes easier to test

AddStoryModal:

function AddStoryModal(props) {
  return (
   <>
  ...
        <Form form={form} layout="vertical" onFinish={props.onFinish}>
  ...
    </>
  );
}

Now it knows nothing about ../api/add_story

And your test becomes:

import AddStoryModal from './AddStoryModal';

describe('Story tests', () => {
  it('makes an API call to add a story', () => {
    const finishCallback = jest.fn();

    render(<AddStoryModal onFinish={finishCallback} />);

    const submitButton = screen.getByTestId('add-submit');
    fireEvent.click(submitButton);
    expect(finishCallback).toHaveBeenCalled();
  });
});

Super-clean test, easy to understand, and a better overall fit for the React way.

In your production code, whatever page was using <AddStoryModal /> now just uses <AddStoryModal onFinish={addStory} />

Related