Search code examples
javascriptreactjsmockingvitevitest

Vitest: Mock a provider's custom useContext in a consumer component in React


The premise

I have the following components:

// providers/SomeContextProvider.tsx

import { createContext, ReactNode, useContext } from 'react';

const SomeContext = createContext('');

export type Props = {
  children: ReactNode;
};
export default function SomeContextProvider(props: Props) {
  const [someCalculatedValue, setSomeCalculatedValue] = useState();

  useEffect(() => {
    //calculate and set the someCalculatedValue
  }, [])

  return (
    <SomeContext.Provider value={someCalculatedValue}>
      {props.children}
    </SomeContext.Provider>
  );
}

export function useSomeContext() {
  return useContext(SomeContext);
}
// SomeConsumer.tsx

import { useSomeContext } from 'providers/SomeContextProvider'

return function SomeConsumer() {
  const contextValue = useSomeContext();

  return (/***/);
}

I want to test SomeConsumer in a way that I can control the "computed value" that it gets from useSomeContext().

In order to do that I am mocking the SomeContextProvider.tsx file by creating a providers/__mocks__/SomeContextProvider.tsx (as per Vitest docs)

//providers/__mocks__/SomeContextProvider.tsx

import { createContext, ReactNode, useContext } from 'react';

const SomeContext = createContext('');

export type Props = {
  children: ReactNode;
  testValue: string; // Notice the mock provider accepts the value that it will pass down, as a prop
};
export default function SomeContextProvider(props: Props) {
  return (
    <SomeContext.Provider value={props.testValue}>
      {props.children}
    </SomeContext.Provider>
  );
}

export function useSomeContext() {
  return useContext(SomeContext);
}

and then mocking the module in the test file:

// SomeConsumer.test.tsx

import SomeConsumer from 'SomeConsumer.tsx';
import SomeContextProvider from 'providers/SomeContextProvider'
import { customRender } from 'test/utils';

vi.mock('providers/SomeContextProvider');

describe('SomeConsumer', function () {
  it('should match snapshot', () => {
    const { asFragment } = customRender(
      <SomeContextProvider testValue={'someValue}>
        <SomeConsumer>{/***/}</SomeConsumer>
      </SomeContextProvider>
    );

    expect(asFragment()).toMatchSnapshot();
  });
});

The problem

The problem is that while the mocked SomeContextProvider is indeed called, can confirm this by setting breakpoints, SomeConsumer is still calling the useSomeContext exported from the original providers/SomeContextProvider.tsx and not the one exported from providers/__mocks__/SomeContextProvider.tsx and as a result this is not working as intended.

The question

How can I make it so that when testing SomeConsumer it is using the mocked useSomeContext that is exported from the mocked SomeContextProvider.tsx file?


Solution

  • Well, nevermind, this seems to work fine after all. Turns out I had forgotten a doMock instead of mock there, while I was testing this. All's well