Mock Function or variable before shallow Jest

Viewed 2647

I am trying to write jest to test my component.But as soon as the shallow is called my test fails by calling the fetchUsers function saying cannot read property get of null (which seems to be the initial value of myRequestor).it is not able to initialize the value of myRequestor in the MyReqUtil functional component inside MyComponent.It looks like it is not calling this function or it is not initializing the value.I am stuck in this as I am not able to write the tests everytime it just fails in the very first line where I use shallow.can anyone please help me on this.

MyComponent.jsx

let myRequestor = null;

 const MyReqUtil = () => {
 myRequestor = React.useContext(MyRequestorContext);
 return <ApplicationLoadingOverlay isOpen backgroundStyle="clear" />;
};


class MyComponent extends React.Component {

componentDidMount() {
  this.setState({ isLoading: true });
  this.fetchUsers();
}

fetchUsers() {
this.setState({ isLoading: true });
const { request } = myRequestor.get({
  url: '/users',
});

request
  .then(({ data }) => {
    if (Object.prototype.hasOwnProperty.call(data[0], 'error')) {
      this.setState({
        error: 'Failure', errorMsg: data[0].error, isOpen: true, isLoading: false, userItems: [{ name: 'DEFAULT', id: 'Default' }],
      });
      return;
    }

    const users = data.map(user => ({
      name: user.shortName,
      id: user.key,
    }));

    this.setState({
      userItems: users, isLoading: false,
    });
  })
  .catch((error) => {
    console.log(error);
    this.setState({ error: 'Failure', isOpen: true, isLoading: false });
  });
 }

 render() {

   if (this.state.tenantItems === null) {
      return <MyReqUtil />;
   }
}

MyComponent.test.jsx

describe('<MyComponent />', () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<MyComponent />);
  });

     it('should check `componentDidMount()`', () => {
        const instance = wrapper.instance();
        jest.spyOn(instance, 'fetchUsers');
        instance.componentDidMount();
        expect(instance.fetchUsers).toHaveBeenCalledTimes(1);
     });

   it('Should Render Component Correctly', () => {
    const spy = jest.spyOn(OrionReqUtil, 'fetchUsers');
    const wrapper = shallow(<MyComponent />);
    const instance = wrapper.instance();
    spy.mockReturnValue(['mockItem1', 'mockItem2', 'mockItem3']);

   });
});
1 Answers

The test does shallow(<MyComponent />) twice, in beforeEach and it, which is likely a mistake. If tests don't fully share the code in beforeEach, it should be moved to tests.

This is the case for disableLifecycleMethods option, it allows to call componentDidMount manually when an instance is prepared:

wrapper = shallow(<MyComponent />, { disableLifecycleMethods: true });
jest.spyOn(wrapper.instance(), 'fetchUsers').mockReturnValue(...);
wrapper.instance().componentDidMount();

For prototype methods, another way is to spy or mock them on class prototype before instantiation:

jest.spyOn(MyComponent.prototype, 'fetchUsers').mockReturnValue(...);
wrapper = shallow(<MyComponent />);

This won't help in this case because MyComponent relies on MyReqUtil child in order to be correctly rendered and mounted, so it's not suitable for being tested with shallow.

This is actually XY problem. Since it's MyReqUtil that causes a problem, it needs to be addressed in the first place. First of all, it's a different unit. This means that it needs to be moved to another module, so it could be mocked with jest.mock.

Also, MyReqUtil establishes unhealthy relationship with MyComponent, it's a child that maintains hidden global state (myRequestor) for a parent and assumes that it won't need it until parent is mounted. This will stop working as intended if there are multiple contexts - even if this doesn't happen in production, this can be needed in tests.

If the only thing it does is providing dependencies to MyComponent, it should be a parent of MyComponent, not the opposite:

const MyReqUtil = (props) => {
  const myRequestor = React.useContext(MyRequestorContext);
  const loaderElement = <ApplicationLoadingOverlay isOpen backgroundStyle="clear" />;

  return <MyComponent loader={loaderElement} requestor={myRequestor} {...props} />
};

This way it can be fully tested as a separate unit. And MyComponent can be fully tested because dependencies are available as props and thus can be mocked.

Related