9
votes

I think I found another way to test a component using the useContext hook. I have seen a few tutorials that test if a value can be successfully passed down to a child component from a parent Context Provider but did not find tutorials on the child updating the context value.

My solution is to render the root parent component along with the provider, because the state is ultimately changed in the root parent component and then passed to the provider which then passes it to all child components. Right?

The tests seem to pass when they should and not pass when they shouldn't. Can someone explain why this is or isn't a good way to test the useContext hook?

The root parent component:

...
const App = () => {
  const [state, setState] = useState("Some Text")

  const changeText = () => {
    setState("Some Other Text")
  }
...

  <h1> Basic Hook useContext</h1>
     <Context.Provider value={{changeTextProp: changeText,
                               stateProp: state
                                 }} >
        <TestHookContext />
     </Context.Provider>
)}

The context object:

import React from 'react';

const Context = React.createContext()

export default Context

The child component:

import React, { useContext } from 'react';

import Context from '../store/context';

const TestHookContext = () => {
  const context = useContext(Context)

  return (
    <div>
    <button onClick={context.changeTextProp}>
        Change Text
    </button>
      <p>{context.stateProp}</p>
    </div>
  )
}

And the tests:

import React from 'react';
import ReactDOM from 'react-dom';
import TestHookContext from '../test_hook_context.js';
import {render, fireEvent, cleanup} from '@testing-library/react';
import App from '../../../App'

import Context from '../../store/context';

afterEach(cleanup)

it('Context is updated by child component', () => {

   const { container, getByText } = render(<App>
                                            <Context.Provider>
                                             <TestHookContext />
                                            </Context.Provider>
                                           </App>);

   console.log(container)
   expect(getByText(/Some/i).textContent).toBe("Some Text")

   fireEvent.click(getByText("Change Text"))

   expect(getByText(/Some/i).textContent).toBe("Some Other Text")
})
1

1 Answers

0
votes

Your example/code is dead on. (Not sure you need to mount the wrapping <App /> - you should just wrap in the Context Provider directly).

To your question:

The tests seem to pass when they should and not pass when they shouldnt. Can someone explain why this is or isnt a good way to test the useContext() hook.

It is a good way to test when using useContext() because it looks like you have abstracted out your context so that your child (consuming) component and its test both use the same context. I don't see any reason why you would mock or emulate what the context provider is doing when (as you do in your example) you use the same context provider.

The React Testing Library docs point out that:

The more your tests resemble the way your software is used, the more confidence they can give you.

Therefore, setting up your tests the same way you set up your components achieves that goal. If you have multiple tests in one app that do need to be wrapped in the same context, this blog post has a neat solution for reusing that logic.