artooras
artooras

Reputation: 6825

Jest mock react context

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.

Upvotes: 94

Views: 167147

Answers (3)

Gustav Vingtoft
Gustav Vingtoft

Reputation: 409

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>
    );
};

}


Upvotes: 2

fjplaurr
fjplaurr

Reputation: 1950

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)
}

Upvotes: 8

Gio Polvara
Gio Polvara

Reputation: 27098

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

Upvotes: 174

Related Questions