Redux Toolkit+Enzyme/Jest测试求助:Mock Store创建及多组件测试
解决Redux Toolkit组件测试的状态污染与Mock Store问题
首先,咱们先搞定你遇到的测试用例状态共享核心问题:你当前测试用的是全局store实例,所有测试用例会共享这个状态,所以递增测试修改状态后,递减测试拿到的是修改后的状态,自然断言失败。咱们需要给每个测试用例创建独立、干净的Store实例。
第一步:修改Root组件,支持传入自定义Store
把你的Root组件改成可以接受可选的store属性,这样测试时能传入临时创建的Store,而非全局默认的那个:
import React from 'react'; import { Provider } from 'react-redux'; import store from 'store/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; store?: typeof store; // 添加可选的store属性 } export default ({ children, store: customStore = store }: IProps) => { return ( <Provider store={customStore}> <GlobalStyle /> {children} </Provider> ); };
第二步:在测试中创建独立的测试Store
Redux Toolkit的configureStore本身就可以用来创建测试用Store,不需要额外依赖redux-mock-store。咱们在beforeEach里每次都新建一个Store实例,确保每个测试都是干净的初始状态:
修改你的计数器测试用例:
import React from 'react'; import { mount } from "enzyme"; import Counter from 'components/counter'; import Root from "root/index"; import { configureStore } from '@reduxjs/toolkit'; import rootReducer from 'store/combineReducer'; // 导入根reducer let wrapped; let testStore; beforeEach(() => { // 每次测试都创建新Store,初始状态与生产环境一致 testStore = configureStore({ reducer: rootReducer }); wrapped = mount( <Root store={testStore}> <Counter /> </Root> ); }); afterEach(() => { wrapped.unmount(); }); describe(`Counter Component Tests`, () => { it(`should render initial counter value as 1`, () => { expect(wrapped.find(`h1`).text()).toEqual(`1`); }); it(`should increment counter to 2 when increment button is clicked`, () => { wrapped.find(`[data-test="increment"]`).simulate(`click`); // Enzyme的mount组件不会自动更新,需手动调用update()获取最新DOM wrapped.update(); expect(wrapped.find(`h1`).text()).toEqual(`2`); }); it(`should decrement counter to 0 when decrement button is clicked`, () => { wrapped.find(`[data-test="decrement"]`).simulate(`click`); wrapped.update(); // 你原来的断言写的是toEqual('2'),这里修正为正确的预期值'0' expect(wrapped.find(`h1`).text()).toEqual(`0`); }); // 测试特定初始状态的场景:比如初始值为5 it(`should decrement from 5 to 4 when button is clicked`, () => { // 创建带自定义初始状态的Store const customStore = configureStore({ reducer: rootReducer, preloadedState: { counter: 5 // 指定counter的初始状态 } }); const customWrapped = mount( <Root store={customStore}> <Counter /> </Root> ); customWrapped.find(`[data-test="decrement"]`).simulate(`click`); customWrapped.update(); expect(customWrapped.find(`h1`).text()).toEqual(`4`); customWrapped.unmount(); }); });
关键说明:
- 每次
beforeEach调用configureStore创建新Store,确保测试完全隔离。 - 通过
preloadedState可以轻松指定任意初始状态,覆盖不同测试场景。 - Enzyme的
mount组件在Redux状态变化后不会自动更新,必须调用wrapped.update()才能获取最新DOM。
第三步:其他组件的测试思路
1. 待办事项(Todo)组件测试
Todo组件的测试逻辑和计数器类似:
- 每次创建干净的Store,可通过
preloadedState传入预设的待办列表。 - 测试添加/删除/修改Todo的操作,模拟用户交互后更新组件,断言DOM变化和Store状态是否符合预期。
- 如果Todo组件涉及异步操作(比如保存到后端),可以用Jest模拟API请求,或者在测试Store中添加中间件捕获异步action。
2. 数据获取(Fetch User)组件测试
对于带异步数据获取的组件(比如用createAsyncThunk的场景):
- 用Jest模拟API请求函数,示例:
jest.mock('your-api-module', () => ({ fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: 'Test User' })) })); - Redux Toolkit的
getDefaultMiddleware默认包含redux-thunk,所以测试Store可以直接处理异步action。 - 测试步骤:
- 挂载组件,触发数据获取操作(比如组件挂载时自动请求)。
- 用
async/await配合wrapped.update()等待Promise完成。 - 断言DOM是否渲染了获取到的数据,或Store中的状态是否正确。
额外提示:单独测试Reducer/Action
如果不需要挂载组件,只想测试Reducer或Action的逻辑,可以直接调用Reducer函数,传入初始状态和Action,断言新状态是否正确:
import counterReducer, { increment, decrement } from 'store/reducer/counter'; test('increment action should increase state by 1', () => { const initialState = 1; const newState = counterReducer(initialState, increment()); expect(newState).toBe(2); }); test('decrement action should decrease state by 1', () => { const initialState = 1; const newState = counterReducer(initialState, decrement()); expect(newState).toBe(0); });
内容的提问来源于stack exchange,提问作者user12494839
相关产品推荐
相关产品推荐

