0

I am testing a react component which has a simple Material-ui switch that updates a boolean field. The component works without any issues when I run the app locally.

In my test, I am mocking the graphql calls via MockedProvider. The mocked provider works as expected, and I can see that the initial response and the update response arrive and they update the state. However, when I find the switch and check it on the screen, it stays unchecked. My test fails with:

Received element is checked: <input checked="" class="PrivateSwitchBase-input-40 MuiSwitch-input" name="callbackEnabled" type="checkbox" value="" />

My first guess is that React doesn't rerender this state change. Do I need to somehow force rerender? Or what is the correct way of testing this kind of behaviour?

The test:

it('should update boolean field', async () => {

const mocks = [
  {
    request: {
      query: myQuery,
      variables: {
        clientId: 'cl_0',
      },
    },
    result: {
      data: {
        myQuery: {
          callbackEnabled: true
        },
      },
    },
  },
  {
    request: {
      query: myMutation,
      variables: {
        clientId: 'cl_0',
        callbackEnabled: false,
      },
    },
    result: {
      data: {
        myMutation: {
          callbackEnabled: false,
        },
      },
    },
  },
];
    let base = null;
 
    await act(async () => {
      const { baseElement, } = render(
        <MockedProvider mocks={mocks} addTypename={false}>
          <MyComponent clientId="cl_0" error={undefined} />
        </MockedProvider>
      );
      base = baseElement;
    });

    // eslint-disable-next-line no-promise-executor-return
    await new Promise((resolve) => setTimeout(resolve, 0));
    // check for info: https://www.apollographql.com/docs/react/development-testing/testing/#testing-the-success-state

    // testing initial state: these pass
    expect(base).toBeTruthy();
    expect(screen.getByRole('checkbox', { name: /callbacks/i })).toBeInTheDocument();
    expect(screen.getByRole('checkbox', { name: /callbacks/i })).toBeChecked();

    // simulate a switch click
    await act(async () => {
      userEvent.click(screen.getByRole('checkbox', { name: 'Callbacks' }));
    });

    // eslint-disable-next-line no-promise-executor-return
    await new Promise((resolve) => setTimeout(resolve, 0)); // wait for response
    
    // fails here
    expect(screen.getByRole('checkbox', { name: /callbacks/i })).not.toBeChecked();
  });
ege
  • 812
  • 6
  • 16

1 Answers1

0

First add a test-id to your checkbox component like this :

<input
  type="checkbox"
  checked={checkboxState}
  data-testid="checkboxID" />

Then in ur test to test if checkbox status was changed :

const input = getByTestId("checkboxID");
// Assuming we set initial status for checkbox component to be false
expect(input.checked).toEqual(false);
fireEvent.click(input);
// check handling click in checkbox
expect(input.checked).toEqual(true);
Rawand Deheliah
  • 1,220
  • 10
  • 11
  • Unfortunately, this doesn't solve the problem above. React still doesn't rerender the state change. – ege Sep 10 '21 at 08:40
  • 2
    @ege - did you ever find a solution to this? Honestly I'm so sick of all the async, mock, blah blah blah blah crap i'm about to write up a whole minimum working example and expose this bug for good now. It's not "that's not how tests work" its not "you have to trigger it again" _its literally broken_ and no one is willing to admit it – fullStackChris Feb 24 '22 at 21:37
  • 1
    @ege - my rage fueled me, feast your eyes: https://codesandbox.io/s/asynchronous-react-redux-toolkit-bug-8sleu4?file=/README.md – fullStackChris Feb 24 '22 at 23:33