如何在测试之间重置 Redux?

Posted

技术标签:

【中文标题】如何在测试之间重置 Redux?【英文标题】:How do I reset Redux between tests? 【发布时间】:2022-01-04 18:43:54 【问题描述】:

有没有人能够在测试之间清除 Redux?你做了什么?

我有一个使用 Redux 的 React Native Expo 应用程序。

我有一个单独运行的测试,但是当我运行所有测试时,它失败了,因为其他测试在 redux 中留下了状态更改。

如何在测试之间“刷新”redux 存储。

我尝试了 mock-redux-store 包,但是当我将 combineReducers() 传递给它时,它什么也没返回。

我可以在那里手动重建状态切片,并将它们传递给模拟存储,但这很难维护。

testing-library-utils.js

//This file adds a Redux Provider to a component's context when running Jest tests from test files.
//This first import enables jest native, which enables some state-centric matchers
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import  render  from '@testing-library/react-native';
import  Provider, combineReducers, applyMiddleware, compose  from 'redux';
import ReduxThunk from 'redux-thunk';
import  createMockStore  from 'redux-test-utils';
// import  store  from '../App';

import logBooksReducer from '../store/reducers/logBooks-reducer';
import authReducer from '../store/reducers/auth-reducer';

const composedMiddleWare = compose(applyMiddleware(ReduxThunk));

const rootReducer = combineReducers(
  logBooks: logBooksReducer,
  auth: authReducer,
);

const store = createMockStore(rootReducer, composedMiddleWare);

console.log('STORE ', JSON.stringify(store.getState()));

const AllTheProviders = ( children ) => 
  console.log('Store.getState() from UTILS ', store.getState());
  return <Provider store=store>children</Provider>;
;

const customRender = (ui, options) =>
  render(ui,  wrapper: AllTheProviders, ...options );

// re-export everything
export * from '@testing-library/react-native';

// override render method
export  customRender as render ;

【问题讨论】:

【参考方案1】:

Redux 是一个定义应用程序当前状态的对象。因此,除非您知道笑话的正确顺序,否则它不会起作用。顺便说一句,Jest 并不是每次都以相同的顺序运行测试。有时它会存储失败的那些并在下一次运行它们。

建议您为每个测试/测试文件升级 Redux 存储。

这是我的test/utils/mocks/redux/index.js


import configureStore from 'redux-mock-store';
import createMockBluetooth from '../bt';
import createMockApi from '../api';
import createMockState from './state';
...


const createMockStore = (
    state = createMockState(),
    api = createMockApi(),
    ble = createMockBluetooth(),
...,
    middlewares = [],
 = ) => 
    const thunkMiddleware = ( dispatch, getState ) => (next) => (action) => (
        typeof action !== 'function' ?
            next(action) :
            action(dispatch, getState,  api, bt, ... )
    );

    return configureStore([thunkMiddleware, ...middlewares])(state);
;

export default createMockStore;

这样我就可以为每个测试定义不同的商店:

addressList.test.js


const defaultProps = 
    navigation: 
        state:  params:  ,
        dispatch: jest.fn(),
 ...
        pop: jest.fn(),
        popToTop: jest.fn(),
        isFocused: jest.fn(),
    ,
;

const getStoreWithMockAddresses = (mockData) =>
    createMockStore(
        state: createMockState(
            listOfAddresses: mockData,
        ,
        
            customMerge: preferOverride('listOfAddresses'),
        ),
    );


....

test('random test', () => 
    const mockAddresses = 
        fullList: [
             name: 'address-1', number: 69 ,
        ],
    ;

   const mockStore = getStoreWithMockAddresses(mockAddresses);

    const  getByTestId  = render(<YourComponent navigation= defaultProps.navigation  />,  wrapper:  store: mockStore  );


我确实需要为每个测试进行设置。这是有道理的,因为我正在测试组件将在特定状态下工作。

请注意,render 方法语法有点像,因为我使用的是原始渲染的自定义实现。

编辑:

import merge from 'deepmerge';

const createMockState = (overrides = , options = []) => merge(
...
    listOfAddresses: 
        fullList: [] ,

, overrides, options);

export default createMockState;

【讨论】:

谢谢!你能帮我理解你的部分代码吗?我看到在上面的 redux 模拟文件中,您从“./state”中引入了状态切片,但是在测试文件中,您再次定义了状态切片。我对定义状态切片/模型的位置感到困惑。你能帮我澄清一下吗? createMockStore 使用“默认”值创建存储。 createMockStatecreateMockApicreateMockBle 返回每​​个相应“存储”的默认值(有“状态存储”、“api 存储”、“ble 存储”等)。对于每个测试,我将覆盖createMockState 给出的默认值,因为我想要当 store.state.listOfAddresses.fullList 具有该条目时组件/应用程序的行为方式。 (默认情况下,createMockStore 返回一个空数组。 我在原始答案中添加createMockStore 的代码 再次感谢。我有点难以跟上。不是你的错,我只是模拟新手,我在单独的“创建”文件中没有我的初始状态。我喜欢你的方法如何从头开始创建一个商店,里面有状态。有没有你知道的我可以一步一步学习的地方? 它也已经存在了。【参考方案2】:

终于明白了!

这是我对Redux Testing Docs中内容的修改版

//This file adds a Redux Provider to a component's context when running Jest tests from test files.
//This first import enables jest native, which enables some state-centric matchers
import '@testing-library/jest-native/extend-expect';
import React from 'react';
import  render as rtlRender  from '@testing-library/react-native'; // This line is important

import  Provider  from 'react-redux';
import  rootReducer  from '../App'; //Importing my same Root Reducer, so maintaining is easier
import  NavigationContainer  from '@react-navigation/native';
import  configureStore  from '@reduxjs/toolkit'; //Need to install @reduxjs/toolkit

const render = (
  ui,
  
    preloadedState,
    store = configureStore(
      reducer: rootReducer,
      preloadedState,
      middleware: getDefaultMiddleware =>
        getDefaultMiddleware(
          serializableCheck: false,
        ),
    ),
    ...renderOptions
   = 
) => 
  const Wrapper = ( children ) => 
    return (
      <Provider store=store>
        <NavigationContainer>children</NavigationContainer> //Adding nav container here
      </Provider>
    );
  ;
  return rtlRender(ui,  wrapper: Wrapper, ...renderOptions );
;

// re-export everything
export * from '@testing-library/react-native';

// override render method
export  render ;

然后,在每个测试的基础上,您将预加载状态传递给测试的渲染函数。这样每个测试都可以有一个单独的 redux 测试环境。

  it('Displays the locaiton', async () => 
    const preloadedState =  entries:  allEntries: myPreloadedStateForThisTest  ; // Make sure to match the data structure of your real state. 

    const  findByText  = render(<EntriesScreen />,  preloadedState );

    // Expect:
    await findByText(/West Virginia/i);
  );

感谢@markerikson 提供examlple code

【讨论】:

以上是关于如何在测试之间重置 Redux?的主要内容,如果未能解决你的问题,请参考以下文章

如何在测试之间重置

如何以 redux 形式重置初始值

在redux中按下重置按钮时如何初始化所有状态

使用 @reduxjs/toolkit 中的 configureStore 时如何重置 Redux Store 的状态?

使用 Redux-Form 进行搜索过滤器,如何“重置”并重新提交表单?

使用 Testcontainers 和 Liquibase 时在测试之间重置数据库