当前位置: 首页 > 知识库问答 >
问题:

使用条件逻辑和getState()用法测试复杂的redux thunks

澹台啸
2023-03-14

我的应用程序中的所有逻辑都存在于action creators(thunks)中。大多数动作创建者的逻辑并不复杂,由条件表达式组成,条件是存储中的值:如果存储中存在此值,则分派这些动作创建者,否则分派此动作。还有一些“聚合器”,它们是动作创建者,通常基于某些状态值的存在来分派其他几个动作创建者;和api包装器,它们使用状态中的参数有条件地调用api抽象thunk,然后处理响应。

关键是,他们中的大多数人使用getState函数来获取他们自己需要的一切,而不是将其作为参数接收。现在,这种方法对我很有用,而且非常简单,但是我很难测试它。到目前为止,我所有的测试都是按照这个建议编写的:https://github.com/reactjs/redux/issues/2179.基本上,在开始时,我使用一些其他操作设置了所需的状态,模拟读取调用,然后调度我打算测试的thunk,然后使用各种选择器。这将在一个测试中同时测试多个动作、减速器和选择器。我喜欢我的测试完全验证特定用例的事实,但是我不确定这是否真的是一个好的实践。我的主要问题是,一些thunks是不可测试的,因为他们派遣了其他5个动作创建者,我很困惑如何至少验证他们被调用,除了检查状态是否改变,这反过来又使promise链变得巨大,并测试相同的功能在多个测试中一遍又一遍。

我对整个测试都不熟悉,互联网上所有的例子都是待办事项列表或其他可笑的简单CRUD应用程序,这些都没有帮助。在使用大量条件逻辑的复杂应用程序中,如何使用依赖于多个状态节点的动作创建者进行redux测试?

共有1个答案

姜振濂
2023-03-14

简短回答:模仿它。=)

详细回答:我个人确实更喜欢在测试中使用尽可能多的真实代码(即,不使用双重测试)。但有时这并不值得,你不得不回过头来嘲笑。

在类似您描述的情况下,您可能希望/需要在测试中检查以下几件事情:

  1. 你测试的时候发出了一些子信号
  2. 测试中的thunk正确处理它发送的sub-thunk的结果。
  3. 测试中的thunk正确处理已更新的状态,该状态由已发送的sub-thunk更新。

根据您想要测试的上述内容的组合,可能会使用不同的策略。例如,如果您的被测thunk依赖于sub thunk的结果,则不需要检查sub thunk是否已被调度:只需模拟sub thunk,以便它返回特定的数据,这些数据将以特定、合理的方式影响被测thunk的行为(请参阅下面代码段中的authmock了解详细信息)。

让我们考虑下面的例子来说明可能的嘲弄策略。假设您必须实现授权功能。假设您的服务器通过httpendpoint对用户进行授权,如果成功,则发送回授权令牌,稍后用于打开websocket连接。假设您以以下方式设计了该功能:

还有connectthunk,它使用用户的登录名和密码发送auth子thunk,然后通过http发送给定的凭证。当服务器响应auth时,thunk将接收到的令牌存储在存储中(仅出于说明性原因)并解析。当auth解析时,connect发送otherStuffthunk,这将使用令牌执行其他操作。最后,connect通过wsApi打开套接字连接。

// =======  connect.js ======= 

import { auth, getToken } from './auth';
import * as wsApi from './ws';
import { otherStuff } from './other-stuff';

export const connect = (login, password) => (dispatch, getState) => {
  // ...

  return dispatch(auth(login, password))
    .then(() => {
      const token = getToken(getState());
      dispatch(otherStuff(token));
      wsApi.connect(token);
    });

  // ...
};


// =======  auth.js ======= 

import * as httpApi from './http';

const saveToken = token => ({ type: 'auth/save-token', payload: token });

export const auth = (login, password) =>
  dispatch =>
  httpApi.login(login, password)
  .then(token => dispatch(saveToken(token)));

export const getToken = state => state.auth.token;

export default (state = {}, action) => action.type === 'auth/save-token' ? { token: action.payload } : state;


// ======= other-stuff.js ======= 

export const otherStuff = token => (dispatch) => {
  // ...
};

我们要做的是模拟两个thunk:authotherStuffconnect高度依赖于auth,因此我们将确保仅通过检查connect行为调用auth,这取决于我们传递给auth的模拟行为。otherStuff的情况要复杂一些。并没有办法检查它是否在实现自定义中间件之后实际被调度,而自定义中间件将记录所有调度的操作。总而言之,测试将如下所示(我使用jest进行模拟):

import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { connect } from './connect';
import { auth, getToken } from './auth';
import { otherStuff } from './other-stuff';
import * as wsApi from './ws';
const authReducer = require.requireActual('./auth').default;

jest.mock('./auth');
jest.mock('./ws');
jest.mock('./other-stuff');

const makeSpyMiddleware = () => {
  const dispatch = jest.fn();
  return {
    dispatch,
    middleware: store => next => action => {
      dispatch(action);
      return next(action);
    }
  };
};

describe('connect', () => {
  let store;
  let spy;

  beforeEach(() => {
    jest.clearAllMocks();
    spy = makeSpyMiddleware();

    store = createStore(authReducer, {}, applyMiddleware(spy.middleware, thunk));

    auth.mockImplementation((login, password) => () => {
      if (login === 'user' && password == 'password') return Promise.resolve();
      return Promise.reject();
    });
  });

  test('happy path', () => {
    getToken.mockImplementation(() => 'generated token');
    otherStuff.mockImplementation(token => ({ type: 'mocked/other-stuff', token }));

    return store.dispatch(connect('user', 'password')).then(() => {
      expect(wsApi.connect).toHaveBeenCalledWith('generated token');
      expect(spy.dispatch).toHaveBeenCalledWith({ type: 'mocked/other-stuff', token: 'generated token'});
    });
  });

  test('auth failed', () => {
    return store.dispatch(connect('user', 'wrong-password')).catch(() => {
      expect(wsApi.connect).not.toHaveBeenCalled();
    });
  });
});

如果您需要对给定代码段的任何评论,请随时询问。

 类似资料:
  • 我正在为一个web应用程序实现一个相当复杂的输入验证,它应该输入一个ID,并将调用其他几个系统,以便在持久化包含该ID的对象之前检查该ID是否有效。我想使用bean验证,但后来我发现自己专门创建了一个bean来允许这种验证发生。事实上,为了进行远程服务调用,我不仅需要向验证器提供ID,还需要提供一些上下文信息。 您认为bean-validation始终是正确的验证位置吗,即使它很复杂并且需要Bea

  • 随着应用程序的增长,在 reducer 逻辑中开始出现一些常见的模式。你可能会发现一部分 reducer 逻辑对于不同类型的数据做着相同的工作,你想通过对每种数据类型复用相同的公共逻辑来减少重复的代码。或者,你可能想要在 store 中处理某个类型的数据的多个”实例“。然而,Redux store 采用全局结构的设计本身就是一种折衷:优点是易于追踪应用程序的整体状态,但是,也可能更难的”命中“那些

  • 在本章中,我们将讨论Axure RP中使用的条件逻辑。 如果 - 然后 - 在Axure中 与任何其他编程工具一样,Axure也支持条件逻辑,以在原型中创建增强的交互。 一旦熟悉了如何提供交互,为交互提供条件逻辑就是下一个层次。 以下是条件逻辑的简单流程 - 如果,单击特定小部件/屏幕 然后,执行特定的操作/交互 否则,保持/更改小部件或屏幕的状态 为了更好地理解这一点,让我们从前面的例子中恢复流

  • 综述 在多功能的动态web应用程序中测试业务逻辑漏洞需要用非常规手段来思考。如果应用认证机制原先以1、2、3的步骤依次执行的验证身份目的来开发,万一用户从步骤1直接跳到步骤3会发生什么?用更加简单的例子来说,在打开失败、权限拒绝或仅仅500的错误的情况下,应用程序是否依然能够提供访问权限? 可以举出许多例子,但是不变的思想是“跳出常规思维”。这种类型的漏洞无法被漏洞扫描工具探测到,依赖于渗透测试人

  • 问题内容: 如何将条件逻辑应用于Pandas DataFrame。 请参见下面显示的DataFrame, 我的原始数据显示在“数据”列中,并且期望的输出显示在其旁边。如果“数据”中的数字小于2.5,则所需的输出为False。 我可以应用循环并重新构建DataFrame …但是那是“非Python的” 问题答案: 只需将列与该值进行比较:

  • 我怎么能在最后运行一些逻辑,而不是收集。 我可以像 我如何在过滤后运行最后的逻辑。 谢谢,拉维