Search code examples
reactjsjestjsreact-testing-library

How can I test functions that are provided by context using jest and react-testing-library?


I have a <UserProvider /> component that provides context which has a login method in its value. In my test I want to check if that method has been called. How can I check to make sure this method is being called in my test if it comes from context? I've tried using the spyOn and mock methods but I can't get them to work.

Here's my UserProvider component which provides the context.

import React, { useState, useContext, createContext } from 'react'

const UserContext = createContext()

function UserProvider({ children }) {
  const [user, setUser] = useState(null)
  const login = user => setUser(user)

  return <UserContext.Provider value={{ user, login }}>{children}</UserContext.Provider>
}

const useUser = () => useContext(UserContext)

export { UserProvider, useUser }

Here's my Login component using the context from UserProvider (via useUser)

import React from 'react'
import { useUser } from './UserProvider'

function Login() {
  const { login } = useUser()

  const handleSubmit = async e => {
    e.preventDefault()

    // I need to make sure this method is being called in my test.
    login({ name: 'John Doe' })
  }

  return (
    <form onSubmit={handleSubmit}>
      <button>Login</button>
    </form>
  )
}

export default Login

Here's my Login test

import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Login from './Login'
import { UserProvider, useUser } from './UserProvider'

// Is this correct?
jest.mock('./UserProvider', () => ({
  useUser: jest.fn(() => ({
    login: jest.fn()
  }))
}))

it('should log a user in', () => {
  const { getByText } = render(
    <UserProvider>
      <Login />
    </UserProvider>
  )

  const submitButton = getByText(/login/i)
  fireEvent.click(submitButton)

  // How can I make this work?
  const { login } = useUser()
  expect(login).toHaveBeenCalledTimes(1)
})

I have a codesandbox but it's erroring out about jest.mock not being a function so I don't know if it's very useful.


Solution

  • I can't get it to work without slightly change the source code a little bit.

    First, I have to export the actual context

    export { UserProvider, useUser, UserContext }
    

    We can re create provider with a mocked login function and the following test will serve you purpose.

    import React from 'react'
    import { render, fireEvent } from '@testing-library/react'
    import Login from './Login'
    import { UserProvider, useUser, UserContext } from './UserProvider'
    
    it('should log a user in', () => {
      const login = jest.fn();
      const { getByText } = render(
        <UserContext.Provider value={{ login }}>
          <Login />
        </UserContext.Provider>
      );
    
      const submitButton = getByText('Login');
      fireEvent.click(submitButton);
    
      expect(login).toHaveBeenCalledTimes(1)
    });
    
    

    It is entirely possible that this is not the best approach. I hope it helps.