Tirion
Tirion

Reputation: 147

In Enzyme, how to get a function from the props of a functional component?

I am writing unit tests for my react project using Jest and Enzyme.

As shown below, I passed a function named updateUser to the to-be-tested component EditCard via props.

describe('The EditCard screen', () => {
  let wrapper;

  beforeEach(() => {
    const defaultProps: Partial<EditCardProps> = {
      toggleEditing: jest.fn(),
      user: mockUsers[0],
      updateUser: jest.fn(),      // passes this function to the "EditCard" component via props
      showSnackbar: jest.fn(),
    };
    wrapper = shallow(<EditCard {...(defaultProps as EditCardProps)} />);
  });

Then I want to test how many times it was called after simulating a click on a button.

  it('should match the snapshot when the "Name" textfield is not filled and the "submit" button is clicked', () => {
    wrapper.find('#Name').simulate('change', { target: { value: null } });
    wrapper.find('#submit').simulate('click');
    
    // Try to get the "updateUser" function from the props, but get "undefined". 
    expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
  });

But I got the error shown below:

    Matcher error: received value must be a mock or spy function

    Received has value: undefined

      24 |     wrapper.find('#Name').simulate('change', { target: { value: null } });
      25 |     wrapper.find('#submit').simulate('click');
    > 26 |     expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);

Could someone tell me where I did wrong? Why I cannot get the function from the props and undefined was returned?

Thanks in advance!

Upvotes: 0

Views: 4106

Answers (1)

Matt Carlotta
Matt Carlotta

Reputation: 19762

A few tweaks to your code should have it working...

import * as React from "react";
import { mount, ReactWrapper } from "enzyme";
import EditCard from "../path/to/EditCard";

/* 
  I'd recommend defining jest fns here to make them easier to reference anywhere
  within the tests below; otherwise, it'll have to referenced via 
  'defaultProps.updateUser', 'defaultProps.showSnackbar', ...etc. 
  Using 'const' here allows us to define these variables within the 
  module's closure -- in short, only accessible within these tests and NOT 
  globally accessible (from other file tests).
*/
const showSnackbar = jest.fn();
const toggleEditing = jest.fn();
const updateUser = jest.fn();

/* 
  if the EditCard component is properly typed, then you shouldn't need to
  add types to this 'defaultProps' object
*/
const defaultProps = {
  showSnackbar,
  toggleEditing,
  updateUser,
  user: mockUsers[0]
};

describe('The EditCard screen', () => {
  let wrapper: ReactWrapper;
  beforeEach(() => {
    /* 
      I'd recommend mount over shallow because child components can be 
      deeply nested and require multiple .dive calls; however, if 
      you know the child components of "EditCard" are just simple JSX elements, 
      then shallow will be fine
    */
    wrapper = mount(<EditCard {...defaultProps} />);
  });

  it("should not call 'updateUser' when the form is submitted with an empty '#Name' field", () => {
    /*
      I'm not sure what simulating "null" does for this input, but assuming this
      input is required you should at least pass a string value -- assuming
      "#Name" input is of type 'text' | 'password' | 'email' => string and 
      not a number. On a related note, if it's required, then simply remove this 
      code as it doesn't do much for the test.
    */
    // wrapper.find('#Name').simulate('change', { target: { value: "" } });

    /*
      I'm assuming this then simulates a form submit. Unfortunately,
      pressing the submit button won't work. Instead you'll have to 
      simulate a form submit. This is a limitation of Enzyme and last I 
      checked hasn't been/can't be fixed.
    */
    wrapper.find('form').simulate('submit');
    
    /*
     it should then NOT call the jest.fn() "updateUser" when submitted since 
     '#Name' is empty. Notice that we're referencing 'updateUser' -- the jest fn
     defined above -- and not the wrapper.prop fn.
    */ 
    expect(updateUser).not.toHaveBeenCalled();
  });
  
  // include other tests...
});


Here's a working example (click the Tests tab to run tests):

Edit Typescript - Form Testing

Upvotes: 1

Related Questions