voodooGQ
voodooGQ

Reputation: 692

Jest with Enzyme, why is `toHaveBeenCalled` not triggering in my React Component test?

I'm working on a unit test in a React application that verifies a passed in prop function is being conditionally called based on another props value. I'm utilizing Typescript/Enzyme/Jest in my application and am using a Root wrapper around the component I'm testing to inject the Redux store (and override initial state if desired).

import { mount, ReactWrapper } from "enzyme";
import React from "react";
import Login from "src/components/auth/Login";
import Root from "src/Root";

let wrapped: ReactWrapper;

let defaultProps = {
  signIn: jest.fn(),
  token: null,
};

beforeEach(() => {
  wrapped = mount(
    <Root>
      <Login {...defaultProps} />
    </Root>
  );
});

describe("on mount", () => {
  describe("if no token is supplied to the props", () => {
    it("will call the props.signIn() function", () => {
      expect(defaultProps.signIn).toHaveBeenCalled();
    });
  });
});

When I run the test the toHaveBeenCalled() (as well as toBeCalled(), tried both) are not registering any calls. However, I have supplied a console.log statement that is getting triggered within the same conditional that the signIn() function lives.

import React from 'react';
import { AuthState, JWT } from "src/components/auth/types";
import { signIn } from "src/redux/auth";

interface Props {
  signIn: () => Promise<void>;
  token: null | JWT;
}

class Login extends React.Component<Props> {
  /**
   * Sign the user in on mount
   */
  public componentDidMount(): void {
    if (!this.props.token) {
      console.log("GETTING HERE");
      this.props.signIn();
    }
  }

  public render(): JSX.Elemeent {
    // ... More code
  }
}

const mapStateToProps = (state: AuthState) => {
  return {
    token: state.auth.token;
  };
};

export default connect(mapStateToProps, { signIn })(Login);

I've gone through several related posts/articles but all of the different configurations, such as traversing enzyme to get the direct prop or utilizing spyOn, have failed.

The only thing I can figure that's different is my wrapping of the Login component with Root, but considering I can see the console.log being triggered this seems like a complete shot in the dark.

Can anyone tell me what I'm doing wrong here?

Upvotes: 0

Views: 1420

Answers (2)

voodooGQ
voodooGQ

Reputation: 692

Ended up being me forgetting to place the override via mapDispatchToProps and mapStateToProps in the connect function. This was causing my passed in signIn function to be overridden by the signIn action imported in the file. Updating with ownProps and conditionally utilizing the passed in value fixes the issue:

const mapStateToProps = (state: AuthState, ownProps: Props) => {
  return {
    token: ownProps.token || state.auth.token;
  };
};

const mapDispatchToProps = (dispatch: ThunkDispatch<{}, {}, any>, ownProps: Props) => {
  return {
    signIn: ownProps.signIn || (() => { return dispatch(signIn()) })
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Login);

Upvotes: 0

Muhammad Ali
Muhammad Ali

Reputation: 2648

You've to wait for component to mount, so:

it("will call the props.signIn() function", (done) => {
  setImmediate(() => {
    expect(defaultProps.signIn).toHaveBeenCalled();
    done()
  });
});

Upvotes: 1

Related Questions