Jest mock react context
Asked Answered
C

3

90

I need some help understanding how one can test an application using React Context.

Here's my sample setup.

context.js

import React from 'react'

export const AppContext = React.createContext()

App.js

import React from 'react'

import MyComponent from './MyComponent'
import {AppContext} from './context'

const App extends React.Component {

  state = {
    items: []
  }

  handleItemAdd = newItem => {
    const {items} = this.state
    items.push(newItem)
    this.setState(items)
  }

  render() {
    return (
      <AppContext.Provider value={{
        addItem: this.handleItemAdd
      }}>
        <MyComponent />
      </AppContext.Provider>
    )
  }
}

export default App

MyComponent.js

import React from 'react'

import {AppContext} from './context'

const MyComponent extends React.Component {    
  render() {
    return (
      <AppContext.Consumer>
        {addItem => 
          <button onClick={() => addItem('new item')}>
            Click me
          </button>
        }
      </AppContext.Consumer>
    )
  }
}

export default MyComponent

This is a simplified example. Imagine that there are more layers between App and MyComponent, hence the use of React Context.

And here's my test file for MyComponent.

MyComponent.test.js

import React from 'react'
import {render, fireEvent} from 'react-testing-library'

test('component handles button click', () => {
  const {getByText} = render(
    <MyComponent />
  )
  const button = getByText('Click me')
  fireEvent.click(button)
  expect...?
}

The thing is, AppContext.Consumer is part of the implementation of MyComponent, so in this test I don't have direct access to it. How do I mock AppContext.Consumer so I am actually able to verify that clicking a button fires a function call?

I know that in theory I can test this by rendering MyComponent in my App, but I want to write a unit test for MyComponent only.

Cutlerr answered 21/1, 2019 at 14:41 Comment(4)
take a look into react-testing-library's docs: testing-library.com/docs/example-react-contextMonique
Thanks for pointing me to the example. So, is manually wrapping MyComponent in AppContext.Provider in each test the only way then?Cutlerr
it looks this way. enzyme's shallow() allows passing in context as an argument. but react-testing-library does not.Monique
@skyboyer, Enzyme's shallow does not work with context in no way.Belva
A
165

You just render the context with your component.

const addItem = jest.fn()
render(
  <AppContext.Provider value={{ addItem }}>
    <MyComponent />
  </AppContext.Provider>
)

See Mocking context with react-testing-library

Allonym answered 22/1, 2019 at 9:1 Comment(0)
C
7

I want to add a complete test example by using the solution from @Giorgio. Here we are testing that MyComponent is rendered and that its button will be clicked once.

MyComponent.test.js

import React from 'react'
import { render, fireEvent } from 'react-testing-library'

test('component handles button click', () => {
  const addItem = jest.fn()
  render(
    <AppContext.Provider value={{ addItem }}>
      <MyComponent />
    </AppContext.Provider>
  )
  fireEvent.click(screen.getByText(/click me/))
  expect(addItem).toHaveBeenCalledTimes(1)
}
Cecelia answered 24/12, 2020 at 23:48 Comment(0)
S
1

In case there are any TypeScript users who can't get this solution to work, I want to add my test example for anyone in distress.

In my case I added a custom component wrapper that can be reused across the testing environment in your application. This can be handy if you are testing more than one component that is nested in the context that you wish to mock.

Also as a bonus I mocked the rxjs behavior subject in case anyone utilizes this library in their tests:

    // Create a mock BehaviorSubject instance
    class MockBehaviorSubject<T> extends BehaviorSubject<T> {
    constructor(initialValue: T) {
        super(initialValue);
    }

    // Override necessary methods
    getValue(): T {
        return this.getValue();
    }

    // Add any other required methods here
    // For example, you might need to add `next` method:
    // next(value: T): void {
    //   this.next(value);
    // }

    // Create a custom wrapper component to provide the mock context values
    export const MockAppProvider: React.FC<WithChildren> = ({ children }) => {
    // Define your mock context values here
    const id= 'test';

    // Create a mock BehaviorSubject instance
    const mockCreateObservablesCallbackSubject = new MockBehaviorSubject<
        TObservableEvent | undefined
    >(undefined);

    return (
        <AppProviderContext.Provider
            value={{
                id: mockCycleId,
                setId: jest.fn(),
                observables: undefined,
                setObservables: jest.fn(),
                createObservablesCallbackSubject: mockCreateObservablesCallbackSubject,
            }}
        >
            {children}
        </AppProviderContext.Provider>
    );
};

}


Sundaysundberg answered 16/6, 2023 at 8:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.