React-Redux-toolkit's: Enzyme-jest 的单元测试

React-Redux-toolkit's: Enzyme-jest's unit test

我正在使用新的 Redux toolkit。新的 Redux-toolkit 比旧的 redux 更高效。我使用新的 Redux 工具包创建了多个 reducer 和 action。我稍微析构了 redux-toolkit 设置。我正在使用 Enzyme 和 Jest 进行单元测试。我的 redux 计数器 intialState 是 1。根据我的测试,在 it 范围内,我首先采用 intialState 然后在 simulate('click') increase 按钮​​之后,我得到了我预期的结果 2。当我尝试在 it 范围内测试我的减少按钮时,它从增加的 it 范围中获取结果。如果我将 intialState 1 放在减少按钮的 it 范围内,它会给我失败的测试,因为它期望 2。我想我需要为此 counter.test 创建 mockStore。由于 redux-toolkit 的语法对我来说是新的,我不知道如何在 counter.test 测试套件中创建 mockstore。

Ps。还有其他用于获取数据的 React 组件,todolist。我也想做 unit test 那些组件。如果有人帮助我测试这些组件,我将非常高兴。我是单元测试的新手。

我将我的代码上传到 Codesandbox

下面我将解释我是如何进行设置并传递给 React 组件的。

这是计数器减速机

import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  "name": `counter`,
  "initialState": 1 as number,
  "reducers": {
    "increment": (state) => state + 1,
    "decrement": (state) => state - 1
  }
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlice.reducer;

这是 combineReducers

import { combineReducers } from 'redux';
import counter from 'store/reducer/counter';
import todo from 'store/reducer/todo/todo';
import fetchUser from 'store/reducer/fetch';
import fetching from 'store/reducer/createAsyncAxios';
const rootReducer = combineReducers({
  counter, //This is my counter Reducer
  todo,
  fetchUser,
  fetching
});

export type IRootState = ReturnType<typeof rootReducer>;
export default rootReducer;

这是我的商店文件

import { configureStore, Action, getDefaultMiddleware } from '@reduxjs/toolkit';
import { ThunkAction } from "redux-thunk";

import rootReducer, { IRootState } from 'store/combineReducer';


const store = configureStore({
  "reducer": rootReducer,
  "middleware": [...getDefaultMiddleware()]
  
});

export type AppThunk = ThunkAction<void, IRootState, null, Action<string>>
export default store;

Ps。我析构根文件。首先我在导入商店的地方创建了一个根文件,然后连接到 App。我这样做是因为我可以直接导入根文件到测试套件

import React from 'react';
import { Provider } from 'react-redux';
import store from 'store/store';
import { createGlobalStyle } from 'styled-components';

const GlobalStyle = createGlobalStyle`
  body {
    background-color: #282c34;
    color: white;
    font-family: "Lucida Console", Courier, monospace;
  }
`;

interface IProps {
  children: JSX.Element[] | JSX.Element;

}
export default ({ children }: IProps) => {
  return (
    <Provider store={store}>
      <GlobalStyle />
      {children}
    </Provider>
  );
};

这就是我的根文件与 App

的连接方式
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import Root from './root';

ReactDOM.render(
  <Root>
    <App />
  </Root>
  ,
  document.getElementById(`root`)
);

这是我的计数器组件

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from 'store/reducer/counter/index';
import { IRootState } from 'store/combineReducer';
import styled from 'styled-components';
const Button = styled.button`
background-color: #4CAF50; /* Green */
  border: none;
  color: white;
  padding: 15px 32px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
`;

const Text = styled.h1`
color: blue;
`;

export default () => {
  const counter = useSelector((state: IRootState) => state.counter);

  const dispatch = useDispatch();
  return (
    <div >
      <Text>{counter}</Text>
      <Button data-test="increment" onClick={() => dispatch(increment())}>
        Increment counter
      </Button>
      <br></br>
      <br></br>
      <Button data-test="decrement" onClick={() => dispatch(decrement())}>
        Decrement counter
      </Button>
    </div>
  );
};

这是我的 counter 测试套件

import React from 'react';
import { mount } from "enzyme"; // mount is full dom renderning function with children
import Counter from 'components/counter';
import Root from "root/index";

let wrapped;
beforeEach(() => {
  // I need to create mock store in here. I don't know how to do that.
  wrapped = mount(
    <Root >
      <Counter />
    </Root>
  );
  // console.log(wrapped.debug());
});

afterEach(() => {
  wrapped.unmount(); // it cleans the mount after test.
});


describe(`This is counter component`, () => {
  it(``, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`);
  });

  it(`after click it will increase the value`, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`);
    wrapped.find(`button`).at(0).find(`[data-test="increment"]`).simulate(`click`);
    expect(wrapped.find(`h1`).text()).toEqual(`2`);
  });
  it(`after click it will decrease the value`, () => {
    expect(wrapped.find(`h1`).text()).toEqual(`1`); // Test failed: because it Received: "2"
    wrapped.find(`button`).at(1).find(`[data-test="decrement"]`).simulate(`click`);
    expect(wrapped.find(`h1`).text()).toEqual(`2`); //
  });
});

我会尝试 redux-mock-store 如果我是你,你可以在每次测试后(或每次测试前)重置你的商店。

这是来自官方文档的例子:

// src/actions/users.test.js 
import mockAxios from "axios";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import promiseMiddleware from "redux-promise-middleware";
import { getUsers } from "./users";

const mockStore = configureMockStore([thunk, promiseMiddleware()]);

describe("User Actions", () => {
  let store;

  beforeEach(() => {
    store = mockStore({
      users: {}
    });
  });

Inside the beforeEach function we reset the value of our store, so we don’t have any unexpected results in our assertions.

您必须将这个模拟商店传递给您的应用程序,最简单的方法是将应用程序中的商店 Provider 上移一级(至 index.js)。然后,你会像这样嘲笑它:

wrapped = mount(
    <Provider store={mockStore}>
        <Root>
            <Counter />
        </Root>
    </Provider>  
);