13

I'm new to React Hooks and what I'm trying to achieve is to test a React component (called CardFooter) that contains a call to useEffect hook that gets triggered a global context variable is modified.

CardFooter.js:

const CardFooter = props => {
  const [localState, setLocalState] = useState({
    attachmentError: false
  });
  const globalContext = useContext(GlobalContext);
  React.useEffect(()=> {
    setLocalState({
    ...localState,
    attachmentError: globalContext.data.attachmentError
  });
 },[globalContext.data.attachmentError]);
}

CardFooter.test.js:

import Enzyme, { shallow } from 'enzyme';    
Enzyme.configure({ adapter: new Adapter() });
describe('<CardFooter  />', () => {
  let useEffect;
  const mockUseEffect = () => {
    useEffect.mockImplementation(f => f());
  };

  useEffect = jest.spyOn(React, "useEffect");
  mockUseEffect(); //

  it('should render correctly with no props.', () => {
  }

  const mockUseEffect = () => {
    useEffect.mockImplementation(f => f());
  };

  useEffect = jest.spyOn(React, "useEffect");
  mockUseEffect();

  const wrapper = shallow(<CardFooter />);
  expect(toJson(wrapper)).toMatchSnapshot();

});

the error that I'm getting when running the test is:

TypeError: Cannot read property 'attachmentError' of undefined

I tried the approach presented here: https://medium.com/@pylnata/testing-react-functional-component-using-hooks-useeffect-usedispatch-and-useselector-in-shallow-9cfbc74f62fb . However it seems that shallow does not pick the mocked useEffect implementation. I also tried mocking the useContext and the globalContext.data.attachmentError. Nothing seems to work.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
otto
  • 161
  • 1
  • 1
  • 6
  • Have you tried moving the mock implementation to module scope (outside of the describe block)? e.g `const useEffect = jest.spyOn(React, "useEffect").mockImplementation(() => {})` just before `describe()`. – Albert Alises Jan 05 '20 at 20:18
  • it works with .mount() and – otto Feb 16 '20 at 15:09

2 Answers2

4

Try this. Notice the "jest.spyOn" is placed inside "beforeEach"

  beforeEach(() => {
    jest.spyOn(React, "useEffect").mockImplementationOnce(cb => cb()());
    
     // .... other things ....
  }
xeiton
  • 1,760
  • 16
  • 20
0

If you want to "trigger" the mocked useEffect, you can do so like this:

useEffect.mock.calls[index][0](useEffect.mock.calls[index][1])

Where index picks the useEffect call by order it was run. For example, if you have this hook:

const useHook = () => {
  useEffect(() => {
    functionToMock1()
  }, []);
  useEffect(() => {
    functionToMock2()
  }, []);
}

You can make the 2nd useEffect trigger with this: useEffect.mock.calls[1][0](useEffect.mock.calls[1][1]), which will then allow you to assert against functionToMock2(). I.e:

jest.mock('../functions', () => ({
  functionToMock1: jest.fn(),
  functionToMock2: jest.fn(),
}))

describe('useHook', () => {
  it('runs functionToMock2', () => {
    useHook();

    // trigger first useEffect
    useEffect.mock.calls[0][0](useEffect.mock.calls[0][1]);
    assert(functions.functionToMock1).toHaveBeenCalled();
    assert(functions.functionToMock2).not.toHaveBeenCalled();

    // trigger second useEffect
    useEffect.mock.calls[1][0](useEffect.mock.calls[1][1]);
    assert(functions.functionToMock2).toHaveBeenCalled();
  })
})
Julian K
  • 1,877
  • 2
  • 19
  • 27