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>
);
我正在使用新的 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>
);