首页 > 解决方案 > fireEvent.click 在进行反应测试时不会对状态进行任何更改

问题描述

我正在尝试使用 react 构建一个简单的计算器应用程序,但在进行一些单元测试时遇到了一些问题。在这里,我附上了我面临一些问题的测试用例。我在网上找到的大多数参考资料都是使用单个组件构建的。在我的情况下,我使用useReducer进行状态管理,使用useContext将数据传递给其他组件,但是在触发click事件时它不会对状态进行任何更改,因此result.textContent始终保持为空字符串(“”)。任何形式的帮助或提示都将得到应用。

应用程序.test.js

import React from "react";
import { render, fireEvent } from "@testing-library/react";
import App from "./App";
import "@testing-library/jest-dom/extend-expect";

describe("Calculator test", () => {
  const { getByTestId } = render(<App />);

  const btn7 = getByTestId("nm-btn-7");
  const btnSum = getByTestId("nm-btn-sum");
  const btn2 = getByTestId("nm-btn-2");
  const btnEql = getByTestId("eq-btn");
  const result = getByTestId("result");

  // expect(btn7.textContent).toBe("7");
  it("should return the correct sum value", () => {
    fireEvent.click(btn7);
    fireEvent.click(btnSum);
    fireEvent.click(btn2);
    fireEvent.click(btnEql);
    console.log(result.textContent);
    expect(result.textContent).toBe("9");
  });
});

应用程序.tsx

import React from "react";
import styled from "styled-components";

import Calculator from "./components/Calculator";
import "./_app.scss";

function App() {
  return (
    <Main>
      <div>
        <Calculator />
      </div>
    </Main>
  );
}

const Main = styled.main`
  display: grid;
  place-items: center;
  min-height: 100vh;
  div {
    /* width: 25%; */
    /* height: 70vh; */
    background-color: #061017;
    border-radius: 0.5em;
  }
`;

export default App;

计算器.tsx

import React from "react";
import styled from "styled-components";

import { useGlobalContext } from "../context/context";
import Buttons from "./Buttons";
import "./_calculator.scss";

const Calculator = () => {
  const { newState } = useGlobalContext();

  const { inputValue } = newState;

  return (
    <Container>
      <section className="form-section">
        <div className="result" data-testid="result">
          {inputValue}
        </div>
      </section>
      <section className="button-section">
        <Buttons />
      </section>
    </Container>
  );
};

const Container = styled.section`
  color: #f2f3f4;
  height: inherit;
  .form-section {
    .result {
      width: 100%;
      padding: 2em 1em;
      font-size: 2em;
      height: 5em;
      border: none;
      outline: none;
      border-radius: 0.5em;
      background-color: #061017;
      color: #f2f3f4;
      text-align: right;
    }
  }
  .button-section {
    padding: 0.5em;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.5em;
  }
`;

export default Calculator;

按钮.tsx

import React, { Fragment } from "react";

import { useGlobalContext } from "../context/context";
import "./_buttons.scss";

const Buttons = () => {
  const {
    handleNumberOpClick,
    handleEqualClick,
    handleClearClick,
    handleBackspaceClick,
  } = useGlobalContext();

  return (
    <Fragment>
      <button className="button-span-2 sp-btn" onClick={handleBackspaceClick}>
        C
      </button>
      <button className="sp-btn" onClick={handleClearClick}>
        AC
      </button>
      {/* <button className="button-blue">+/-</button> */}
      <button
        className="button-blue nm-btn"
        onClick={handleNumberOpClick}
        name="/"
      >
        &divide;
      </button>
      <button
        className="button-gray"
        data-testid="nm-btn-7"
        onClick={handleNumberOpClick}
        name="7"
      >
        7
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="8"
      >
        8
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="9"
      >
        9
      </button>
      <button
        className="button-blue nm-btn"
        onClick={handleNumberOpClick}
        name="*"
      >
        &times;
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="4"
      >
        4
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="5"
      >
        5
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="6"
      >
        6
      </button>
      <button
        className="button-blue nm-btn"
        onClick={handleNumberOpClick}
        name="-"
      >
        -
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="1"
      >
        1
      </button>
      <button
        className="button-gray"
        data-testid="nm-btn-2"
        onClick={handleNumberOpClick}
        name="2"
      >
        2
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="3"
      >
        3
      </button>
      <button
        className="button-blue"
        data-testid="nm-btn-sum"
        onClick={handleNumberOpClick}
        name="+"
      >
        +
      </button>
      <button
        className="button-span-2 button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="0"
      >
        0
      </button>
      <button
        className="button-gray nm-btn"
        onClick={handleNumberOpClick}
        name="."
      >
        .
      </button>
      <button
        className="nm-btn"
        data-testid="eq-btn"
        onClick={handleEqualClick}
      >
        =
      </button>
    </Fragment>
  );
};

export default Buttons;

上下文.tsx

import React, { useContext, useReducer } from "react";

import { reducer } from "../reducer/reducer";
import { IState, IValue } from "../interfaces";

const initialState: IState = {
  inputValue: "",
};

const initialContextState: IValue = {
  newState: initialState,
  handleNumberOpClick: function () {},
  handleEqualClick: function () {},
  handleClearClick: function () {},
  handleBackspaceClick: function () {},
};

const AppContext = React.createContext<IValue>(initialContextState);

const AppProvider = ({ children }: { children: any }) => {
  const [newState, dispatch] = useReducer(reducer, initialState);

  const handleNumberOpClick = (e: any) => {
    e.preventDefault();
    let newInputValue = newState.inputValue || "";
    if (
      newState.inputValue === "Invalid" ||
      newState.inputValue === "Infinity"
    ) {
      dispatch({
        type: "ON_NUMBER_OP_CLICK",
        payload: e.target.name,
      });
      return;
    }
    newInputValue = newInputValue.concat(e.target.name);
    dispatch({ type: "ON_NUMBER_OP_CLICK", payload: newInputValue });
  };

  const handleEqualClick = () => {
    try {
      if (
        newState.inputValue === "Invalid" ||
        !newState.inputValue ||
        newState.inputValue === "Infinity"
      ) {
        dispatch({
          type: "ON_EQUAL_CLICK",
          payload: "",
        });
        return;
      }
      dispatch({
        type: "ON_EQUAL_CLICK",
        payload: eval(newState.inputValue).toString(),
      });
    } catch (err) {
      dispatch({
        type: "ON_EQUAL_CLICK",
        payload: "Invalid",
      });
    }
  };

  const handleClearClick = () => {
    dispatch({ type: "ON_CLEAR_CLICK" });
  };

  const handleBackspaceClick = () => {
    const newInputValue = newState.inputValue.slice(0, -1) || "";
    dispatch({ type: "ON_BACKSPACE_CLICK", payload: newInputValue });
  };

  return (
    <AppContext.Provider
      value={{
        newState,
        handleNumberOpClick,
        handleEqualClick,
        handleClearClick,
        handleBackspaceClick,
      }}
    >
      {children}
    </AppContext.Provider>
  );
};

export const useGlobalContext = () => {
  return useContext(AppContext);
};

export { AppContext, AppProvider };

减速器.ts

import { IAction, IState } from "../interfaces";

export const reducer = (currentState: IState, action: IAction): IState => {
  const { type, payload } = action;

  if (type === "ON_NUMBER_OP_CLICK") {
    return { ...currentState, inputValue: payload };
  }

  if (type === "ON_EQUAL_CLICK") {
    return { ...currentState, inputValue: payload };
  }

  if (type === "ON_CLEAR_CLICK") {
    return { ...currentState, inputValue: "" };
  }

  if (type === "ON_BACKSPACE_CLICK") {
    return { ...currentState, inputValue: payload };
  }

  return currentState;
};

接口.ts

export type IType =
  | "ON_NUMBER_OP_CLICK"
  | "ON_CLEAR_CLICK"
  | "ON_BACKSPACE_CLICK"
  | "ON_EQUAL_CLICK";

export interface IState {
  inputValue: string;
}

export interface IAction {
  type: IType;
  payload?: any;
}

export interface IValue {
  newState: IState;
  handleNumberOpClick: any;
  handleEqualClick: any;
  handleClearClick: any;
  handleBackspaceClick: any;
}

标签: javascriptreactjsunit-testing

解决方案


您确实在没有 AppProvider 的情况下测试了 App 组件,该组件在索引文件中使用,在 AppComponent 中移动 AppProvider 并且您的测试工作正常。


推荐阅读