How to use jest.spyOn with React function component using Typescript

codermav picture codermav · May 24, 2019 · Viewed 7.8k times · Source

I am developing a React app using Typescript, and hooks, and I am trying to use Enzyme with Jest to test the function components. I am unable to use jest.spyOn to test a method in my component. The jest.spyOn method doesn't resolve correctly and shows following message on hover

"Argument of type '"validateBeforeSave"' is not assignable to parameter of type '"context" | "setState" | "forceUpdate" | "render" | "componentDidMount" | "shouldComponentUpdate" | "componentWillUnmount" | "componentDidCatch" | "getSnapshotBeforeUpdate" | ... 6 more ... | "UNSAFE_componentWillUpdate"'.ts(2345)"

I tried to cast the instance as 'Any' -

const instance = wrapper.instance() as any;

This of course ignores the problem at compile time, but then the test throws a runtime error that function does not exist on component.

Cannot spy the validateBeforeSave property because it is not a function; undefined given instead

// Some function Component

const SomeComponent = (props: IMyComponentProps) => {
  const { classes } = props;

  // Component has state
  const [count, setCount] = useState(0);

  function validateBeforeSave(){

  }

  function handleClick() {
  validateBeforeSave();
  .
  .
  .
  }

  return (
   <div>
      <Button>
      className="saveBtn"
      onClick={handleClick}
      </Button>
    </div>
  );

  };

  // Unit test
  describe('SomeComponent' () => {
  it('validates model on button click', () => {
      const wrapper = mount(
        <MuiThemeProvider theme={theme}>
          <SomeComponent/>
        </MuiThemeProvider>,
      );
  const instance = wrapper.instance();
      const spy = jest.spyOn(instance, "validateBeforeSave");
  wrapper
        .find('.saveBtn')
        .at(0)
        .simulate('click');
      expect(spy).toHaveBeenCalledTimes(1);
    });
  }

What am I missing here? How does spyOn work with function components?

I created the app using the create-react-app template and it has these dependencies for test packages

"devDependencies": {
    "ts-jest": "^23.10.3",
    "@types/jest": "24.0.9",
    "@types/enzyme": "^3.9.1",
    "@types/enzyme-adapter-react-16": "^1.0.2",
    "enzyme": "^3.9.0",
    "enzyme-adapter-react-16": "^1.11.2",
    "enzyme-to-json": "^3.3.5",
  }

Answer

Rikin picture Rikin · May 24, 2019

Posting my comment here again to answer your question: Your validateBeforeSave function is declared within SomeComponent making it a closed/private scope function not accessible outside. You can pass that function as a prop and you can then create spy and pass it as a prop value in your test and test for if the prop function passed (spy) was called or not

So you would modify your function somewhat like this:

// some validator function
function validateBeforeSave(){
  ...
}

// Some function Component

const SomeComponent = (props: IMyComponentProps) => {
  const { classes, validateBeforeSave } = props;

  // Component has state
  const [count, setCount] = useState(0);


  function handleClick() {
  validateBeforeSave();
  .
  .
  .
  }

  return (
   <div>
      <Button>
      className="saveBtn"
      onClick={handleClick}
      </Button>
    </div>
  );

};

And In your Unit test, something like this:

  // Unit test
  describe('SomeComponent' () => {
  it('validates model on button click', () => {
      const validateSpy = jest.fn();
      const wrapper = mount(
        <MuiThemeProvider theme={theme}>
          <SomeComponent validateSpy={validateSpy}/>
        </MuiThemeProvider>,
      );
      const instance = wrapper.instance();
      wrapper
        .find('.saveBtn')
        .at(0)
        .simulate('click');
      expect(validateSpy).toHaveBeenCalledTimes(1);
    });
  }