Testing React custom hook - how to mock useContext value with Jest

Recently, I’ve had to write a test for a react custom hook with Jest and react-hooks-testing-library that uses useContext. It was certainly not straight forward to figure out how to mock useContext and the values that it returns - at least for me. Fortunately, I was able to write the test to write about it in this post :)

Setup

I’ve built a prototype in CodeSandbox that somewhat resembles my use-case. The prototype is super simple - users can add expenses and see those in a list. It’s the expense version of the todo app.

Let’s look at the code of ExpensesContextProvider that fetches expenses in useEffect and passes the expenses as well as the state setter to the value.

// ExpensesContextProvider.js

import React, { createContext, useState, useEffect } from 'react';
import expensesPromise from './expensesPromise';

export const expenseContext = createContext({});

export default function ExpensesContextProvider({ children }) {
  const [expenses, setExpenses] = useState([]);

  useEffect(() => {
    (async () => {
      const expensesFromPromise = await expensesPromise();

      setExpenses([...expensesFromPromise]);
    })();
  }, []);

  return (
    <expenseContext.Provider value={{ expenses, setExpenses }}>
      {children}
    </expenseContext.Provider>
  );
}

let’s look at the custom hooks that use these values and return a function to use as the event handler to the onSubmit event.

// useExpenses.js

import { useContext } from 'react';
import { expenseContext } from './ExpensesContextProvider';

const useExpenses = () => {
  const { expenses, setExpenses } = useContext(expenseContext);

  const addExpense = expense => {
    setExpenses([...expenses, expense]);
  };

  return { addExpense };
};

export default useExpenses;

We’re going to write a test for the above custom hook.

The test

I’ll explain the testing process step by step. First, we import all the needed dependencies.

import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import useExpenses from '../useExpenses';
import { expenseContext } from '../ExpensesContextProvider';

According to the guide in the react-hooks-testing-library documentation, we need to provide a wrapper with the context provider. Let’s make the wrapper.

const ExpensesContextProvider = ({ children }) => (
  <expenseContext.Provider>{children}</expenseContext.Provider>
);

const wrapper = ({ children }) => (
  <ExpensesContextProvider>{children}</ExpensesContextProvider>
);

Now. we’ll mock the useContext and provide a mock implementation. In our case, it also provides state setter. We need to mock that as well.

let resultExpense = [];

const mockSetExpenses = jest.fn().mockImplementation(expense => {
  resultExpense = [...expense];

  return resultExpense;
});

const mockUseContext = jest.fn().mockImplementation(() => ({
  expenses: [],
  setExpenses: mockSetExpenses,
}));

React.useContext = mockUseContext;

The benefit of making setExpenses is that we’ll be able to test if it gets called or not and the number of times it gets called and so on.

Then, we’ll write assertions.

describe('useExpenses', () => {
  it('should add expense', () => {
    const { result } = renderHook(() => useExpenses(), { wrapper });

    expect(resultExpense).toHaveLength(0);

    result.current.addExpense({
      id: '1',
      item: 'p',
      price: '2',
    });

    expect(mockSetExpenses).toHaveBeenCalled();
    expect(resultExpense).toHaveLength(1);
  });
});

You can see the file here.

I acknowledge that the example follows unnecessary complexity but I hope that you get the general idea of how to mock useContext in react custom hook :)

Share on

Get the latest post in your inbox!

Kamal Sharif
Hi, I'm Md. Kamal Sharif. I'm a senior frontend developer based in Dhaka, Bangladesh.

Follow Blog

Posts you might like

Git Rebase - how to squash multiple commits into a single commitWhy I decided to become a software engineer at the age of 26Advance usage of Array.prototype.reduce() in JavaScript