4

I am trying to determine how best to mock Providers when Jest testing with React Context. The most promising lead I've found is RTL, which provides the following example in their docs: https://testing-library.com/docs/example-react-context

I have been experimenting with multiple structures, but even when I follow their example closely I get the above error.

Here I am following their example almost exactly when testing my Dialog component:

Jest Test

import React from 'react';
import { render } from '@testing-library/react';
import { DialogContext, defaultState } from '../../../../store/contexts/dialogContext';

const customRender = (ui, { providerProps, ...renderOptions }) => {
  return render(
    <DialogContext.Provider {...providerProps}>{ui}</DialogContext.Provider>,
    renderOptions
  );
};

it('Dialog should work', async () => {
  const providerProps = {
    dialogType: 'CANCEL',
    ...defaultState,
  };
  const { container } = customRender(<Dialog />, { providerProps });
  const results = await axe(container);

  expect(results).toHaveNoViolations();
});

Dialog Component

import React, { useContext } from 'react';
import { DialogContext } from '../../../store/contexts/dialogContext';

export default function Dialog() {
  const [dialogState, dialogDispatch] = useContext(DialogContext);

  //logic

 return (
  <div id='dialog'/>
  );
}

This is a simplified version of my Dialog Component, but I receive the following error as soon as it invokes useContext() on line 2.

Error: Uncaught [TypeError: (0 , _react.useContext) is not a function or its return value is not iterable]

As 'React' is in scope in the relevant files, and I'm following the RTL example closely, I'm stumped. Solutions or recommendations for other ways to mock Providers welcome.

EDIT: Adding the dialogContext file for clarity. The component and the context work fine when not in the Jest flow.

import React, { createContext, useReducer } from 'react';
import { dialogReducer } from '../reducers/dialogReducer';

export const DialogContext = createContext({});
const { Provider } = DialogContext;

export const defaultState = {
  //state object
}

export const DialogProvider = ({ children }) => {
  const [state, reducer] = useReducer(dialogReducer, defaultState);

  return <Provider value={[state, reducer]}>{children}</Provider>
}

and when DialogContext is logged from within the Dialog component (the line before), it looks as expected:

{ '$$typeof': Symbol(react.context),
        _calculateChangedBits: null,
        _currentValue: undefined,
        _currentValue2: {},
        _threadCount: 0,
        Provider: { '$$typeof': Symbol(react.provider), _context: [Circular] },
        Consumer:
         { '$$typeof': Symbol(react.context),
           _context: [Circular],
           _calculateChangedBits: null },
        _currentRenderer: {},
        _currentRenderer2: null }
JKL
  • 83
  • 2
  • 8
  • Whether `{ useContext }` works is very specific to your Jest config, the error is ambiguous, but it's more likely that useContext doesn't return an array while it's assumed by destructuring. Nothing in the code you posted suggests that the context was provided with array value. – Estus Flask Sep 26 '20 at 20:00
  • Can you give me more information on the Jest config? We have a generic Jest set up with CRA but I can change or override that. If it's not obvious, the component and Context works when not in this mocked flow, but I will add the Context file for clarity. – JKL Sep 26 '20 at 20:35
  • If you use CRA then there should be no problem with Jest config, at least in this respect. Thanks. It's the difference between test and prodiction code that causes a problem here. – Estus Flask Sep 26 '20 at 21:02

2 Answers2

2

The error is ambiguous and can really occur when useContext is undefined and when useContext is a function but doesn't return an iterable (array).

The first situation can happen if Jest was incorrectly configured and react package is imported as CommonJS module that is translated to ES module default import via module interop.

The second situation is the most simple explanation for the error. Context provider wasn't provided with array value so useContext doesn't return an array. Unless defaultState object contains value pro...perty with array value, ...defaultState spread will result in wrong provider value.

Provider value should be provided in tests the same way it was in the app:

<Provider value={[state, reducer]}>...

If the intention is to not use dialogReducer in the test, a spy can be provided instead:

<DialogContext.Provider value={[defaultState, mockedDispach]}>...

Otherwise DialogProvider can be used instead of DialogContext.Provider because it already provides expected provider value.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • You are absolutely right, thank you! When I pass in the original reducer to the mocked Provider `{ui}` and fixed the syntax when I passed in state to the customRender `customRender(, { testState })`, it works great ---- Thank you!! – JKL Sep 26 '20 at 23:40
  • Can you please elaborate on what and where to do in the first situation? – ShashankAC Aug 03 '23 at 07:48
  • @ShashankAC Usually Jest and Babel/Typescript configs need to be fixed. Can't say more precisely, this depends on your case. Sticking to the configs from a template instead of writing it from scratch may help. If this is the case for you, consider asking a new question that reflects it – Estus Flask Aug 03 '23 at 07:55
0
jest.mock('react', () => ({
    ...jest.requireActual('react'),
    useContext: jest.fn(),
}))

Add this line before configuring the adapter.

configure({adapter: new Adapter()});