There are multiple ways to mock an event handler in Jest Testing Framework. I will discuss two different scenarios and further, we will introspect each case.
Scenario1: Component accepts event handler as a prop
export const ProductView = ({ product, onClick }) => {
...
};
We need to test the onClick
when the component is clicked. jest.fn is used to mock the handler.
const handleClick = jest.fn();
test('when clicked, calls onClick with productId', async() => {
render('<ProductView product={product} onClick={handleClick} />')
// click on the ProductView
userEvent.click(screen.getByTestId('product'))
// expect mock handler to be called
expect(handleClick).toBeCalledTimes(1);
expect(handleClick).toBeCalledWith(product.id)
})
PS: This is possible only because onClick
is exposed as a prop. Another scenario to catch is if the component handled the click internally.
Scenario 2: Component handles the event internally
import { CartService } from '../../services';
export const ProductViewStandalone = ({
product,
}) => {
const { id, name, description, price, photo } = product;
const handleClick = async () => {
await CartService.addProduct(id);
};
return (
<div testId="product" onClick={handleClick}>
...
</div>
);
};
In this situation, we can't mock the event handler because it is internal to the component. However, we know it calls CartService, which is an ES6 module.
Solution 1: Use jest.mock to mock the entire CartService module
Instead of mocking the event handler, we can mock the entire CartService module using jest.mock
// import CartService module so that we can mock it
import { CartService } from '../../services';
// automock the entire CartService module
jest.mock('../../services/CartService');
test('should calls addProduct with productId when clicked', async () => {
const { getByTestId } = render(<ProductViewStandalone product={product} />)
// click on the ProductView
userEvent.click(getByTestId('product'));
// expect addProduct to be called
expect(CartService.addProduct).toBeCalledTimes(1);
expect(CartService.addProduct).toBeCalledWith(product.id)
});
Solution 2: Use jest.mock with an explicit module implementation
This is same as solution 1, except that we provide an explicit module implementation instead of automocking the module.
const mockAddProduct = jest.fn();
// mock the CartService module and provide an implementation
jest.mock('../../services/CartService', () => {
CartService: {
addProduct: async (productId) => {
// call mock function with productId to check later in test
mockAddProduct(productId);
return {
items: [{productId, productName: 'iPhone', price: 899, quantity: 1}];
}
}
});
test('should calls onClick with productId when clicked', async () => {
const { getByTestId } = render(<ProductViewStandalone product={product} />)
// click on the ProductView
userEvent.click(getByTestId('product'))
// expect addProduct to be called
expect(mockAddProduct).toBeCalledTimes(1);
expect(mockAddProduct).toBeCalledWith(product.id);
});
Solution 3: Use jest.spyOn to mock an individual method
In this approach, we only mock one function in the CartService module. This is
done using
jest.spyOn.
jest.synOn
creates a mock function similar to jest.fn
but also tracks calls
to the specified method. For example:
// import CartService module so that we can mock it
import {CartService} from '../../services';
test('should calls onClick with productId when clicked', async () => {
const spyAddProduct = jest.spyOn(CartService, 'addProduct');
const { getByTestId } = render(<ProductViewStandalone product={product} />)
// click on the ProductView
userEvent.click(getByTestId('product'))
// expect spyAddProduct to be called
expect(spyAddProduct).toBeCalledTimes(1);
expect(spyAddProduct).toBeCalledWith(product.id);
})
Motivation: https://github.com/nareshbhatia/react-testing-techniques
Know More: https://medium.com/engineered-publicis-sapient/react-testing-techniques-d97e9dd8f081