Redux - 测试
Redux - 测试
Section titled “Redux - 测试”测试 Redux 应用通常非常简单,因为 Redux 主要依赖于纯函数(pure functions),特别是 reducer。Action 通常是简单的对象,而 action creator 往往是返回这些对象的简单函数。这使得单元测试(unit testing)无需大量模拟(mocking)即可轻松进行。
我们将使用 Jest 作为我们的测试框架。Jest 是一个流行的 JavaScript 测试框架,它在 Node.js 环境中运行良好,并且对于大多数 Redux 测试来说不需要 DOM。
如果您还没有安装 Jest,请运行:
npm install --save-dev jest# oryarn add --dev jest# orpnpm add --save-dev jest如果您使用 Babel 或 TypeScript,请确保 Jest 配置正确。对于 Babel,您可能需要安装 babel-jest:
npm install --save-dev babel-jest @babel/core @babel/preset-env# oryarn add --dev babel-jest @babel/core @babel/preset-env# orpnpm add --save-dev babel-jest @babel/core @babel/preset-env通过 babel.config.js 或 .babelrc 配置 Babel。一个简单的配置可能如下所示:
// babel.config.jsmodule.exports = { presets: [ ['@babel/preset-env', {targets: {node: 'current'}}], // Add other presets like @babel/preset-react or @babel/preset-typescript if needed ],};将测试脚本添加到您的 package.json 文件中:
{ "scripts": { "test": "jest", "test:watch": "jest --watch" }}使用 npm test、yarn test 或 pnpm test 运行测试。
测试 Action Creator
Section titled “测试 Action Creator”Action creator 是返回 action 对象的函数。测试它们涉及调用该函数并断言返回的对象与期望的 action 匹配。
考虑这个简单的 action creator:
// actions/itemActions.jsexport const ITEMS_REQUEST_SUCCESS = 'items/requestSuccess';
export const itemsRequestSuccess = (isLoading) => ({ type: ITEMS_REQUEST_SUCCESS, payload: { isLoading },});这个 action creator 的测试用例如下:
// actions/itemActions.test.jsimport { itemsRequestSuccess, ITEMS_REQUEST_SUCCESS } from './itemActions';
describe('item actions', () => { it('itemsRequestSuccess should create an action with isLoading payload', () => { const isLoading = false; const expectedAction = { type: ITEMS_REQUEST_SUCCESS, payload: { isLoading }, }; expect(itemsRequestSuccess(isLoading)).toEqual(expectedAction); });});如果您使用 Redux Toolkit 的 createSlice,action creator 会自动生成。您通常通过 reducer 测试或集成测试(integration tests)来测试它们的效果。
测试 Reducer
Section titled “测试 Reducer”Reducer 必须是纯函数(pure functions):给定相同的 state 和 action,它们必须总是返回相同的新 state,且没有副作用(side effects)或突变(mutations)。
考虑一个简单的 reducer(可能是 Redux Toolkit 生成的 slice 的一部分):
// reducers/itemReducer.jsconst initialState = { isLoading: false, items: [],};
const itemReducer = (state = initialState, action) => { switch (action.type) { case 'items/requestStart': return { ...state, isLoading: true, }; case 'items/requestSuccess': return { ...state, isLoading: action.payload.isLoading, // 假设 payload: { isLoading: boolean } }; // 带有 items payload 的例子 case 'items/set': return { ...state, items: action.payload, // 假设 payload 是一个 items 数组 }; default: return state; }};
export default itemReducer;要测试这个 reducer,提供一个初始 state,分发(dispatch)一个 action,并断言返回的 state 是正确的:
// reducers/itemReducer.test.jsimport itemReducer from './itemReducer';
describe('itemReducer', () => { const initialState = { isLoading: false, items: [], };
it('should return the initial state if no state is provided', () => { expect(itemReducer(undefined, {})).toEqual(initialState); });
it('should handle items/requestStart action', () => { const action = { type: 'items/requestStart' }; const expectedState = { ...initialState, isLoading: true, }; expect(itemReducer(initialState, action)).toEqual(expectedState); });
it('should handle items/requestSuccess action', () => { const previousState = { ...initialState, isLoading: true }; const action = { type: 'items/requestSuccess', payload: { isLoading: false }, }; const expectedState = { ...initialState, isLoading: false, }; expect(itemReducer(previousState, action)).toEqual(expectedState); });
it('should handle items/set action', () => { const action = { type: 'items/set', payload: [{ id: 1, name: 'Test Item' }], }; const expectedState = { ...initialState, items: [{ id: 1, name: 'Test Item' }], }; expect(itemReducer(initialState, action)).toEqual(expectedState); });});测试异步 Action (Thunks)
Section titled “测试异步 Action (Thunks)”测试异步 action creator(例如使用 createAsyncThunk 创建的 thunk 或手动创建的 thunk)通常涉及模拟 API 调用并检查哪些 action 被分发了。
可以使用像 redux-mock-store 这样的库,或者通过模拟 dispatch 和 getState 直接测试 thunk。
使用 Jest 模拟的例子(概念性):
// actions/asyncActions.test.jsimport { createAsyncThunk } from '@reduxjs/toolkit';import * as api from '../api'; // 你的 API 调用模块
// Assume fetchItems is an async thunk// const fetchItems = createAsyncThunk('items/fetch', async () => { ... });
jest.mock('../api'); // 模拟 API 模块
describe('async actions', () => { it('fetchItems dispatches pending and fulfilled actions on success', async () => { const mockDispatch = jest.fn(); const mockGetState = jest.fn(); const mockData = [{ id: 1, name: 'Fetched Item' }]; api.fetchItemsFromApi.mockResolvedValue(mockData); // 模拟 API 调用
// Assume fetchItems is your async thunk function // await fetchItems()(mockDispatch, mockGetState, undefined);
// Assertions would depend on how fetchItems is implemented (e.g., using createAsyncThunk) // 断言将取决于 fetchItems 如何实现(例如,使用 createAsyncThunk) // Example: Check if pending action was dispatched // 示例:检查 pending action 是否被分发 // expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({ type: 'items/fetch/pending' })); // Example: Check if fulfilled action was dispatched with correct payload // 示例:检查 fulfilled action 是否带有正确的 payload 被分发 // expect(mockDispatch).toHaveBeenCalledWith(expect.objectContaining({ // type: 'items/fetch/fulfilled', // payload: mockData, // })); });});对于更复杂的场景或集成测试,考虑使用 Testing Library 等库配合真实的 Redux store 进行测试。
有关更多详细信息,请参阅官方文档:
- Jest 文档: https://jestjs.io/
- Redux Toolkit 测试指南: https://redux.js.org/usage/writing-tests