Search code examples
reactjsjestjsreact-testing-libraryuse-contextuse-reducer

React Testing Library with UseReducer & UseContext not updating state properly


I created a sandbox to outline the main points of interest: https://codesandbox.io/s/restless-dawn-nwy0l

Please ignore the formatting as this is just a MWE I put together.

When I run the following test in the sandbox above

 
import React from "react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";

import Statistics from "../src/components/Block/Statistics";
import { IAction, IState } from "../src/typings/AppTypes";
import { AppReducer } from "../src/reducers/AppReducer";
import { AppContext } from "../src/context/AppContext";
import * as MineUtil from "../src/utils/mine";
import * as ConversionUtil from "../src/utils/conversion";

const state: IState = {
  verifiedTrans: [
    {
      to: "A",
      from: "B",
      amount: 1.23,
      message: "First Transaction",
      signature: "AB1.23"
    },
    {
      to: "A",
      from: "C",
      amount: 456.78,
      message: "Second Transaction",
      signature: "AC456.78"
    },
    {
      to: "A",
      from: "D",
      amount: 999.99,
      message: "Third Transaction",
      signature: "AD999.99"
    },
    {
      to: "A",
      from: "E",
      amount: 987.65,
      message: "Forth Transaction",
      signature: "AE987.65"
    },
    {
      to: "A",
      from: "F",
      amount: 1.01,
      message: "Fifth Transaction",
      signature: "AF1.01"
    }
  ],
  selectedTrans: [
    {
      to: "A",
      from: "C",
      amount: 456.78,
      message: "Second Transaction",
      signature: "AC456.78"
    }
  ],
  chain: [
    {
      index: 0,
      prevHash: "",
      currHash: new Array(64).fill("0").join(""),
      transactions: [],
      timestamp: Date.parse("04/31/2021"),
      merkleRoot: "",
      valid: true
    },
    {
      index: 1,
      prevHash: new Array(64).fill("0").join(""),
      currHash: new Array(64).fill("A").join(""),
      transactions: [
        {
          to: "A",
          from: "E",
          amount: 987.65,
          message: "Forth Transaction",
          signature: "AE987.65"
        }
      ],
      timestamp: Date.parse("05/01/2021"),
      merkleRoot: "987.65EForthTransactionAE987.65A",
      valid: true
    }
  ],
  preview: {
    index: 2,
    timestamp: Date.parse("05/02/2021"),
    prevHash: new Array(64).fill("A").join(""),
    currHash: "",
    transactions: [],
    merkleRoot: "",
    valid: false
  }
};

const dispatch = (action: IAction) => AppReducer(state, action);

it("keeps mining button disabled after mining due to valid solution", async () => {
  const solution =
    "000a4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";
  const target =
    "000b4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";

  jest.spyOn(MineUtil, "createTarget").mockReturnValue(Promise.resolve(target));
  jest
    .spyOn(ConversionUtil, "digestMessage")
    .mockReturnValue(Promise.resolve(solution));

  render(
    <AppContext.Provider value={{ state, dispatch }}>
      <Statistics chain={false} />
    </AppContext.Provider>
  );

  expect(screen.getByRole("button", { name: /Block Mine/i })).toBeEnabled();

  // need to await state changes
  fireEvent.click(screen.getByRole("button", { name: /Block Mine/i }));
  await waitFor(() => {
    expect(screen.getByRole("button", { name: /Block Mine/i })).toBeDisabled();
  });

  await waitFor(() => {
    expect(
      screen.getByRole("textbox", { name: /Block Solution/i })
    ).toHaveClass("valid-solution");
  });
});

 
 

I get:

result of test

The test simply checks that if you press the mine button once there are selected transactions, the button becomes disabled, and once a valid solution is mined it's class changes and it thus becomes green text (for this component). The button should also remain disabled as the solution is valid.

Additionally the preview block will turn green but this is outside the scope of this unit test.

However, it seems like the state preview block (which determines the class on the solution input textbox) does not update properly in the test environment. During development this is not the case and everything works as expected.

Any suggestions/hints?


Solution

  • In your test, you setup a mock version of value, dispatch which does not trigger update when you click on Block mine button.

    But in actual code, in App.tsx you use useReducer hook (deep down it trigger re-render and new props is passed to Statistics via Context).

    To fix it, just simulate your test with useReducer

      const Wrapper = () => {
        const [state, dispatch] = useReducer(AppReducer, inititalValues);
    
        return (
          <AppContext.Provider value={{ state, dispatch }}>
            <Statistics chain={false} />
          </AppContext.Provider>
        );
      };
    
      render(<Wrapper />);
    

    inititalValues is the variable state on line 12

    Working sandbox here

    Output: enter image description here