如何 对 react 状态库进行单测, 比如 redux、recoil 等状态库【热度: 170】

关键词:前端单测模拟 react 状态

下面分别介绍如何对 Redux 和 Recoil 这两种常见的 React 状态库进行单元测试。

测试 Redux

Redux 是一个可预测的状态容器,主要由 actions、reducers 和 store 构成。单元测试时可分别对这些部分进行测试。

1. 测试 Actions

Actions 是用于描述状态变化的普通 JavaScript 对象。可以测试 action 创建函数是否返回正确的 action 对象。

// actions.js
export const increment = () => ({
  type: "INCREMENT",
});

// actions.test.js
import { increment } from "./actions";

describe("increment action", () => {
  test("should return an action with type INCREMENT", () => {
    const action = increment();
    expect(action.type).toBe("INCREMENT");
  });
});

2. 测试 Reducers

Reducers 是纯函数,接收当前状态和 action,返回新的状态。可以测试 reducer 在不同 action 下是否返回正确的状态。

// reducer.js
const initialState = {
  count: 0,
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {
        ...state,
        count: state.count + 1,
      };
    default:
      return state;
  }
};

export default counterReducer;

// reducer.test.js
import counterReducer from "./reducer";

describe("counterReducer", () => {
  test("should handle INCREMENT action", () => {
    const initialState = { count: 0 };
    const action = { type: "INCREMENT" };
    const newState = counterReducer(initialState, action);
    expect(newState.count).toBe(1);
  });

  test("should return the same state for unknown action", () => {
    const initialState = { count: 0 };
    const action = { type: "UNKNOWN_ACTION" };
    const newState = counterReducer(initialState, action);
    expect(newState).toEqual(initialState);
  });
});

3. 测试 Connected Components

如果组件通过 connect 函数连接到 Redux store,可以使用 enzyme@testing-library/react 来测试组件是否正确接收和使用 store 中的状态和 actions。

// CounterComponent.jsx
import React from "react";
import { connect } from "react-redux";
import { increment } from "./actions";

const CounterComponent = ({ count, increment }) => (
  <div>
    <p>Count: {count}</p>
    <button onClick={increment}>Increment</button>
  </div>
);

const mapStateToProps = (state) => ({
  count: state.count,
});

const mapDispatchToProps = {
  increment,
};

export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent);

// CounterComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { Provider } from "react-redux";
import configureStore from "redux-mock-store";
import CounterComponent from "./CounterComponent";

const mockStore = configureStore([]);

describe("CounterComponent", () => {
  let store;

  beforeEach(() => {
    store = mockStore({ count: 0 });
  });

  test("should display the count from the store", () => {
    render(
      <Provider store={store}>
        <CounterComponent />
      </Provider>
    );
    const countElement = screen.getByText("Count: 0");
    expect(countElement).toBeInTheDocument();
  });

  test("should dispatch increment action on button click", () => {
    render(
      <Provider store={store}>
        <CounterComponent />
      </Provider>
    );
    const incrementButton = screen.getByText("Increment");
    fireEvent.click(incrementButton);
    const actions = store.getActions();
    expect(actions).toEqual([{ type: "INCREMENT" }]);
  });
});

测试 Recoil

Recoil 是一个用于管理 React 应用状态的库,主要包含 atoms(原子状态)和 selectors(派生状态)。

1. 测试 Atoms

Atoms 是 Recoil 中的基本状态单元。可以测试 atoms 的初始值是否正确。

// atoms.js
import { atom } from "recoil";

export const counterState = atom({
  key: "counterState",
  default: 0,
});

// atoms.test.js
import { counterState } from "./atoms";
import { useRecoilValue } from "recoil";
import { renderHook } from "@testing-library/react-hooks";

describe("counterState", () => {
  test("should have an initial value of 0", () => {
    const { result } = renderHook(() => useRecoilValue(counterState));
    expect(result.current).toBe(0);
  });
});

2. 测试 Selectors

Selectors 是基于 atoms 或其他 selectors 派生出来的状态。可以测试 selectors 是否正确计算派生状态。

// selectors.js
import { atom, selector } from "recoil";

export const counterState = atom({
  key: "counterState",
  default: 0,
});

export const doubleCounterSelector = selector({
  key: "doubleCounterSelector",
  get: ({ get }) => {
    const count = get(counterState);
    return count * 2;
  },
});

// selectors.test.js
import { counterState, doubleCounterSelector } from "./selectors";
import { useRecoilValue, set } from "recoil";
import { renderHook } from "@testing-library/react-hooks";

describe("doubleCounterSelector", () => {
  test("should return double the counter value", () => {
    const { result } = renderHook(() => useRecoilValue(doubleCounterSelector));
    expect(result.current).toBe(0);

    const { set } = renderHook(() => ({
      set: (value) => useSetRecoilState(counterState)(value),
    })).result.current;

    set(5);
    const { result: newResult } = renderHook(() => useRecoilValue(doubleCounterSelector));
    expect(newResult.current).toBe(10);
  });
});

3. 测试 Recoil 组件

可以测试使用 Recoil 状态的组件是否正确更新和渲染。

// CounterComponent.jsx
import React from "react";
import { useRecoilState } from "recoil";
import { counterState } from "./atoms";

const CounterComponent = () => {
  const [count, setCount] = useRecoilState(counterState);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default CounterComponent;

// CounterComponent.test.js
import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { RecoilRoot } from "recoil";
import CounterComponent from "./CounterComponent";

describe("CounterComponent", () => {
  test("should display the count and increment on button click", () => {
    render(
      <RecoilRoot>
        <CounterComponent />
      </RecoilRoot>
    );
    const countElement = screen.getByText("Count: 0");
    expect(countElement).toBeInTheDocument();

    const incrementButton = screen.getByText("Increment");
    fireEvent.click(incrementButton);

    const newCountElement = screen.getByText("Count: 1");
    expect(newCountElement).toBeInTheDocument();
  });
});

通过以上方法,可以对 Redux 和 Recoil 状态库进行有效的单元测试,确保状态管理逻辑的正确性。