Mocking an event handler

Mocking an event handler

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