How to test React useEffect hooks with jasmine and enzyme - reactjs

I can't find how to call my useEffect hooks while testing my component.
I tried several solution like this one, but it didn't work: https://reactjs.org/docs/test-utils.html#act
My component :
const mapDispatchToProps = (dispatch: IDispatch, ownProps: ITextAreaOwnProps): ITextAreaDispatchProps => ({
onMount: () => dispatch(addTextArea(ownProps.id)),
});
export const TextArea = (props) => {
React.useEffect(() => {
props.onMount();
}, []);
// more code... //
return (
<>
<TextareaTagName
{...props.additionalAttributes}
className={props.className}
/>
{props.children}
{getValidationLabel()}
</>
);
};
My test :
it('should call prop onMount on mount', () => {
const onMount = jasmine.createSpy('onMount');
mount(<TextArea id="textarea-id" onMount={onMount} />);
expect(onMount).toHaveBeenCalledTimes(1);
});

Regarding the documentation, useEffect should update on any prop change.
You can just recall the test using other props.
You can mock the use effect.
/* mocking useEffect */
useEffect = jest.spyOn(React, "useEffect");
mockUseEffect(); // 2 times
mockUseEffect(); //
const mockUseEffect = () => {
useEffect.mockImplementationOnce(f => f());
};

Short answer is you can't test it directly. You basically need to trigger the change detection in your component to activate the useEffect hook. You can easily use the react-dom/test-utils library by doing something like this
import { act } from 'react-dom/test-utils';
import { shallow, mount } from 'enzyme';
it('should call prop onMount on mount', () => {
const onMount = jasmine.createSpy('onMount');
const wrapper = mount(<TextArea id="textarea-id" onMount={onMount} />);
act(() => {
wrapper.update();
});
expect(onMount).toHaveBeenCalledTimes(1);
});

The way you can test the onMount function call is by calling the wrapper.update() method. You have to use mount from enzyme as shallow doesn't support calling hooks yet. More about the issue here -
useEffect not called when the component is shallow rendered #2086
import { mount } from 'enzyme';
it('should call prop onMount on mount', () => {
// arrange
const onMount = jasmine.createSpy('onMount');
const wrapper = mount(<TextArea id="textarea-id" onMount={onMount} />);
// act
wrapper.update();
// assert
expect(onMount).toHaveBeenCalledTimes(1);
});
In case you are making an api call in the useEffect hook, you have to update the wrapper in setTimeout hook so that the changes are reflected on the component.
import { mount } from 'enzyme';
it('should call prop onMount on mount', () => {
// arrange
const onMount = jasmine.createSpy('onMount');
const wrapper = mount(<TextArea id="textarea-id" onMount={onMount} />);
setTimeout(() => {
// act
wrapper.update();
// assert
expect(onMount).toHaveBeenCalledTimes(1);
});
});

Related

How to test onClick with Jest that is NOT a callback function in props?

I found lots of ways of using mock functions in jest to spy on callback functions that are passed down to a component but nothing on testing a simple onClick that is defined in the same component.
My Example Page:
const ExamplePage: NextPage = () => {
const router = useRouter();
const onClick = (): Promise<void> => {
axios.post(`/api/track`, {
eventName: Event.TRACK_CLICK,
});
router.push("/new-route");
return Promise.resolve();
};
return (
<Container data-testid="container">
<Title>Example Title</Title>
<CreateButton data-testid="create-button" onClick={onClick}>
Create Partner
</CreateButton>
</Container>
);
};
export default ExamplePage;
My current test where I am attempting to get the onClick from getAttribute:
import { fireEvent, render } from "../../../../test/customRenderer";
import ExamplePage from "../../../pages/example-page";
describe("Example page", () => {
it("has a button to create", () => {
const { getByTestId } = render(<ExamplePage />);
const createButton = getByTestId("create-button");
expect(createButton).toBeInTheDocument();
});
it(" the button's OnClick function should be executed when clicked", () => {
const { getByTestId } = render(<ExamplePage />);
// find the button
const createButton = getByTestId("create-button");
// check the button has onClick
expect(createButton).toHaveAttribute("onClick");
// get the onClick function
const onClick = createButton.getAttribute("onClick");
fireEvent.click(createButton);
// check if the button's onClick function has been executed
expect(onClick).toHaveBeenCalled();
});
});
The above fails since there is no onClick attribute only null. My comments in the test highlight my thought process of trying to reach down into this component for the function on the button and checking if it has been called.
Is there any way to test a onClick that is self contained in a react component?
You need to provide mocked router provider and expect that a certain route is pushed to the routers. You also need extract the RestAPI into a separate module and mock it! You can use Dependency Injection, IOC container or import the Api in the component and mock it using jest. I will leave the RestAPi mocking to you.
Mocking router details here: How to mock useRouter
const useRouter = jest.spyOn(require('next/router'), 'useRouter')
describe("", () => {
it("",() => {
const pushMock = jest.fn();
// Mocking Rest api call depends on how you are going to "inject it" in the component
const restApiMock = jest.jn().mockResolvedValue();
useRouter.mockImplementationOnce(() => ({
push: pushMock,
}))
const rendrResult = render(<ExamplePage />);
//get and click the create button
//expect the "side" effects of clicking the button
expect(restApiMock).toHaveBeenCalled();
expect(pushMock).toHaveBeenCalledWith("/new-route");
});
});

Dispatch a Custom Event and test if it was correctly triggered (React TypeScript, Jest)

I am trying to validate a custom event listener that lies inside a react function useEffect hook as shown below:
export interface specialEvent extends Event {
detail?: string
}
function Example() {
React.useEffect(()=>{
document.addEventListener('specialEvent', handleChange)
return () => {
document.removeEventListener('specialEvent',handleChange)
}
})
const handleChange = (event:SpecialEvent) => {
...
}
}
I want to trigger this custom event listener and test it in jest:
it('should trigger "specialEvent" event Listener Properly', async () => {
const specialEvent = new CustomEvent('specialEvent')
const handleChange = jest.fn()
render(<Example />)
await waitFor(() => {
window.document.dispatchEvent(specialEvent)
expect(window.document.dispatchEvent).toHaveBeenNthCalledWith(1, 'specialEvent')
expect(specialEvent).toHaveBeenCalledTimes(1)
})
})
This code gives me the following error:
expect(received).toHaveBeenNthCalledWith(n, ...expected)
Matcher error: received value must be a mock or spy function
Received has type: function
Received has value: [Function dispatchEvent]
As suggested in one of the answers, I tried this:
//Assert Statements
const specialEvent = new CustomEvent('specialEvent');
const handleSelect = jest.fn();
act(() => {
render(<Example />)
});
await waitFor(() => {
window.document.dispatchEvent(specialEvent)
expect(handleSelect).toHaveBeenCalledTimes(1)
});
But this time it says expected call to be 1 but recieved 0.
Can someone help me resolving this?
When testing, code that causes React state updates should be wrapped into act(...). If the handleChange does not cause the React state to update, you don't need to use act.
Besides, it's better not to test the implementation detail, for your case, the test implementation detail statements are:
expect(window.document.dispatchEvent).toHaveBeenNthCalledWith(1, 'specialEvent')
expect(specialEvent).toHaveBeenCalledTimes(1)
Every small change to the implementation detail will cause the test case need to be modified. We should test the UI from the perspective of the user, who doesn't care about the implementation details of the UI, only about rendering the UI correctly.
What you should test is: what happens to the output of the component when the custom event is fired and the state is changed in the event handler.
E.g.
index.tsx:
import React, { useState } from 'react';
export interface SpecialEvent extends Event {
detail?: string;
}
export function Example() {
const [changed, setChanged] = useState(false);
React.useEffect(() => {
document.addEventListener('specialEvent', handleChange);
return () => {
document.removeEventListener('specialEvent', handleChange);
};
});
const handleChange = (event: SpecialEvent) => {
console.log(event);
setChanged((pre) => !pre);
};
return <div>{changed ? 'a' : 'b'}</div>;
}
index.test.tsx:
import { render, screen, act } from '#testing-library/react';
import '#testing-library/jest-dom/extend-expect';
import React from 'react';
import { Example } from './';
describe('70400540', () => {
test('should pass', () => {
const specialEvent = new CustomEvent('specialEvent');
render(<Example />);
expect(screen.getByText('b')).toBeInTheDocument();
act(() => {
window.document.dispatchEvent(specialEvent);
});
expect(screen.getByText('a')).toBeInTheDocument();
});
});
window.document.dispatchEvent(specialEvent) will cause the React state to change, so we wrap it into act(...).
Test result:
PASS examples/70400540/index.test.tsx (11.259 s)
70400540
✓ should pass (59 ms)
console.log
CustomEvent { isTrusted: [Getter] }
at Document.handleChange (examples/70400540/index.tsx:16:13)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.655 s
package versions:
"#testing-library/react": "^11.2.2",
"react": "^16.14.0",
"jest": "^26.6.3",
As the error message says, the toHaveBeenNthCalledWith matcher requires a mock or spy to be passed to expect.
However, you probably do not need to make any assertion about window.document.dispatchEvent being called because you know you are calling it on the line above in your test.
For more information, check the docs on toHaveBeenNthCalledWith here: https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-

React useEffect hook jest unit test doesn't confirm function prop is called

I'm trying to write a unit test to check that a function (passed as a prop) gets called if another prop is true in useEffect hook. The unit test fails to confirm that the (mocked) function is called in the useEffect hook, but it can confirm that a function that is spyOn from an imported module is called. Does anyone know what might be the issue? Thanks!
import {getUser} from './Auth';
export function ComponentA({
shouldRetryExport,
someReduxDispatchFunc,
}) {
const handleExport = useCallback(async () => {
const user = await getUser();
someReduxDispatchFunc();
}, []);
useEffect(() => {
if (shouldRetryExport) {
handleExport();
}
}, [shouldRetryExport]);
return (<SomeComponent />)
});
Unit test:
import * as Auth from './Auth';
it('should call someReduxDispatchFunc if getUserAuthorization is true', () => {
const getAuthUserSpy = jest.spyOn(Auth, 'getUser');
const someReduxDispatchFuncMock = jest.fn();
const props = {
someReduxDispatchFunc: someReduxDispatchFuncMock,
shouldRetryExportWithUserReAuthorization: true,
};
enzyme.mount(<ComponentA {...props} />);
expect(getAuthUserSpy).toHaveBeenCalled(); // works -> returns true
expect(someReduxDispatchFuncMock).toHaveBeenCalled(); // doesn't work -> returns false
});
It seems like it has something to do with the useCallback with useEffect. If I remove the useCallback and add the logic within to useEffect, it can capture someReduxDispatchFuncMock has been called.
I don't think the problem is from either useCallback or useEffect. The problem is most likely your callback takes an async function which means it needs time to get resolved.
In order to this, you have to make your test as async then wait it to get resolved as following:
it('should call someReduxDispatchFunc if getUserAuthorization is true', async () => {
const getAuthUserSpy = jest.spyOn(Auth, 'getUser');
const someReduxDispatchFuncMock = jest.fn();
const props = {
someReduxDispatchFunc: someReduxDispatchFuncMock,
shouldRetryExport: true,
};
enzyme.mount(<ComponentA {...props} />);
// wait for getting resolved
await Promise.resolve();
expect(getAuthUserSpy).toHaveBeenCalled();
expect(someReduxDispatchFuncMock).toHaveBeenCalled();
});

Test multiple React component state changes using Test Renderer

The component that I am trying to test is having state isSubmitting which is set true when we try to submit the form and then it is set back to false when we receive the response from server.
I want to test the state of component after each state update.
const Component = () => {
const [isSubmitting, setIsSubmitting] = useState();
const handlePress = async () => {
setIsSubmitting(true);
await submitForm();
setIsSubmitting(false);
};
return (
<>
<button onPress={() => this.handlePress} />
{isSubmitting && <IsSubmitting />}
</>
)
}
I am able to test only the first component state eg. when I update isSubmitting to true.
import React from 'react';
import { act, create } from 'react-test-renderer';
import Component from './Component';
it('shows loading screen after submit, and hides it after', async () => {
jest.spyOn(form, 'submitForm').mockResolvedValue();
const wrapper = create(<Component />);
act(() => {
wrapper.root.findByType(Button).props.onPress();
});
expect(wrapper.root.findByType(IsSubmitting)).toBeDefined();
});
How can I check that the IsSubmitting component is hidden afterwards? I am also getting an error for not wrapping update into act().
console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:104
Warning: An update to Component inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/docs/test-utils.html#act
in Component
I had to call act function twice. For the first time without await and for the second time with async await.
const wrapper = create(<Component />);
act(() => {
wrapper.root. findByType(Button).props.onPress();
});
expect(wrapper.root.findByType(IsSubmitting)).toBeDefined();
await act(async () => {});
expect(wrapper.root.findAllByType(IsSubmitting)).toStrictEqual([]);'
This way, I am able to test component in both states.

How to test a React Component with a Custom Hook

Given a simple custom hook (which I know how to test):
const useCounter = () => {
const [value, setValue] = useState(0)
const increase = () => {
setValue(value + 1)
}
return {
value,
increase
}
}
And a component that use it:
const App = (props) => {
const { value, increase } = useCounter()
return (
<div>
<div>{value}</div>
<button onClick={increase}>
plus
</button>
</div>
)
}
How do I test the component?
What is the "right way" of doing it?
Do I have to mock the custom hook? How?
Assuming you are working with Jest and Enzyme for unit testing, I would wrap the App component into a shallow wrapper using Enzyme's shallow rendering API.
Then, I will use .find() to find the button element, and use .simulate('click') on the element, to simulate an onClick event on the button, such that the increase method will be called.
After which, I will carry on to check with the expected result and behaviour.
Here is a brief on how it can be done:
import { shallow } from 'enzyme';
import * as React from 'react';
describe('<App />', () => {
it('test for onClick', () => {
const wrapper = shallow(<App />);
wrapper.find('button').simulate('click');
// do the rest here
// expect().....
});
})
You should not need to mock it. When you run App in a test it will just use your custom hook.
If you want to modify it's behaviour somehow specifically for the test, then you would have to mock it.

Resources