1

What would be the correct way to test that a component has updated its parent context?

Say from the example below, after MsgSender has been clicked, how can I verify that MsgReader has been updated?

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

const MsgReader = React.createContext()
const MsgWriter = React.createContext()

const MsgProvider = ({ init, children }) => {
  const [state, setState] = React.useState(init)
  return (
    <MsgReader.Provider value={state}>
      <MsgWriter.Provider value={setState}>{children}</MsgWriter.Provider>
    </MsgReader.Provider>
  )
}

const MsgSender = ({ value }) => {
  const writer = React.useContext(MsgWriter)
  return (
    <button type="button" onClick={() => writer(value)}>
      Increment
    </button>
  )
}

describe('Test <MsgSender> component', () => {
  it('click updates context', async () => {
    const { getByRole } = render(
      <MsgProvider init={1}>
        <MsgSender value={2} />
      </MsgProvider>,
    )

    const button = getByRole('button')
    await act(async () => fireEvent.click(button))
    
    // -> expect(???).toBe(2)
  })
})

The cleanest way I've managed to come up with is to manually set the *.Providers, but I'm wondering if this is perhaps the wrong way to go about it.

it('click updates context with overrides', async () => {
  let state = 1
  const setState = (value) => {
    state = value
  }

  const { getByRole } = render(
    <MsgReader.Provider value={state}>
      <MsgWriter.Provider value={setState}>
        <MsgSender value={2} />
      </MsgWriter.Provider>
    </MsgReader.Provider>,
  )
  const button = getByRole('button')

  expect(state).toBe(1)
  await act(async () => fireEvent.click(button))
  expect(state).toBe(2)
})
0

1 Answer 1

2

You need to create a customRender which gives you the ability to assert the state like this:


function customRender(ui, { init, ...options }) {
  const [state, setState] = React.useState(init);

  function wrapper({ children }) {
    return (
      <MsgReader.Provider value={state}>
        <MsgWriter.Provider value={setState}>{children}</MsgWriter.Provider>
      </MsgReader.Provider>
    );
  }

  return {
    ...render(ui, { wrapper, ...options }),
    state,
  };
}

describe("Test <MsgSender> component", () => {
  it("click updates context", async () => {
    const { getByRole, state } = customRender(<MsgSender value={2} />);

    const button = getByRole("button");
    await act(async () => fireEvent.click(button));

    expect(state).toBe(2)
  });
});
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for this, however your example throws Invalid hook call. Hooks can only be called inside of the body of a function component. when I try to run it.
@kgreen yeah, you're right, I didn't think about that

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.