【问题标题】:Testing async redux with third party API calls使用第三方 API 调用测试异步 redux
【发布时间】:2017-05-08 00:27:05
【问题描述】:

我是 redux 和一般编程的新手,我无法理解某些单元测试概念。

我在 redux 中有一些异步操作,其中涉及对第三方 API 的调用(来自“amazon-cognito-identity-js”节点模块)。

我已将外部 API 调用封装在一个 Promise 函数中,并从“实际”动作创建者调用此函数。因此,对于测试,我只想对 externalAWS() 函数的结果进行存根,以便检查是否正在调度正确的操作。

我正在为我的中间件使用 redux-thunk。

import { AuthenticationDetails,
         CognitoUser
} from 'amazon-cognito-identity-js';

export function externalAWS(credentials) {

  //This is required for the package
  let authenticationDetails = new AuthenticationDetails(credentials);

  let cognitoUser = new CognitoUser({
  //Construct the object accordingly
  })

  return new Promise ((resolve, reject) => {

    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: result => {
        resolve(result);
      },
      onFailure: error => {
        reject(error)
      }
    })
  }
}

export function loginUser(credentials) {

  //return function since it's async
  return dispatch => {

    //Kick off async process
    dispatch(requestLogin());

    externalAWS(credentials)
      .then((result) => {
        dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
      })
      .catch((error) => {
        dispatch(failedLogin(error.message, etc))
      })
  }
}

我还没有任何测试代码,因为我真的不确定如何处理这个问题。所有示例都处理模拟 HTTP 请求,我知道是 这归结为什么,所以我应该在浏览器中检查 HTTP 请求并直接模拟它们吗?

authenticateUser 的第二个参数甚至不是一个普通的回调,而是一个将回调作为其值的对象,这一事实更加复杂。

谁能就我对异步函数进行单元测试的意图是否正确以及我应该如何处理它提供一些建议?谢谢。

编辑:我正在 Jest 中测试。

Edit2:请求标头 First POST request, Second POST request

Edit3:拆分函数,尽我所能隔离外部 API 并创建“易于模拟/存根”的东西。但仍然遇到如何正确存根此函数的问题。

【问题讨论】:

    标签: node.js unit-testing redux amazon-cognito redux-thunk


    【解决方案1】:

    Redux thunk 使您能够在启动流程的主要操作的上下文中调度未来的操作。这个主要动作是您的 thunk 动作创建者。

    因此,测试应该关注根据 api 请求的结果在您的 thunk 动作创建者中调度哪些动作

    测试还应该查看传递给您的动作创建者的参数,以便您的减速器可以了解请求的结果并相应地更新存储。

    要开始测试您的 thunk 操作创建者,您需要测试这三个操作是否根据登录是否成功而正确分派。

    1. 请求登录
    2. 接收登录
    3. 登录失败

    这是我为您编写的一些测试,用于开始使用 Nock 拦截 http 请求。

    测试

    import nock from 'nock';
    
    const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
    
    const fakeCredentials = {
        username: 'fakeUser'
        token: '1234'
    }
    
    it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
    
      nock(API_URL)
        .post( ) // insert post request here  e.g - /loginuser
        .reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
    
      return store.dispatch(loginUser(fakeCredentials))
        .then(() => {
          const expectedActions = store.getActions();
          expect(expectedActions.length).toBe(2);
          expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
          expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
        })
    });
    
    it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
    
      nock(API_URL)
          .post( ) // insert post request here  e.g - /loginuser
          .reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
    
      return store.dispatch(loginUser(fakeCredentials))
        .then(() => {
          const expectedActions = store.getActions();
          expect(expectedActions.length).toBe(2);
          expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
          expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
        })
    });
    

    【讨论】:

    • 谢谢,这有助于解释测试异步操作创建者背后的想法。但是,我的测试仍然失败,因为原来的 authenticateUser 函数仍在运行。我知道这一点是因为它失败了,该 API 的文档中出现异常(“找不到用户”,可能来自“假用户”)。
    • 顺便说一下,mock响应也是我最不熟悉的部分,所以不知道从这里排错。
    • 调用 api 时请求的 url 是什么?
    • 有许多来自authenticateUser 的HTTP 请求。特别是对cognito-idp.us-west-2.amazonaws.com 的两个 POST 请求
    • 好吧,对不起,我应该意识到这是我们需要存根的函数。我已经更新了示例中的代码。现在应该模拟 authenticateUser 函数,因此返回我们在测试中定义的已解析承诺。运行它并让我知道是否仍在调用真正的 api。由于您尚未共享 amazon-cognito-identity-js 文件,我认为我无法为自己运行代码。
    【解决方案2】:

    所以我最终想通了。 首先,我必须将模块 require() 到我的测试文件中(而不是 ES6 导入)。然后我暂时删除了这个 Promise,因为它增加了一层复杂性并将所有内容组合到一个函数中,我们称之为loginUser()。它是一个 redux 异步操作,在被调用时分派一个操作,然后根据 API 调用的结果分派一个成功或失败的操作。有关 API 调用的内容,请参见上文。

    然后我写测试如下:

    const CognitoSDK = require('/amazon-cognito-identity-js')
    const CognitoUser = CognitoSDK.CognitoUser
    
    //Set up the rest of the test
    
    describe('async actions', (() => {
      it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
        let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
          callback.onSuccess(mockResult)
        })
        store.dispatch(loginUser(mockData))
        expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
      }))
    }))
    

    所以基本上一旦需要这个模块,我就在 Jest 中模拟它并做了一个模拟实现,这样我就可以访问回调对象的 onSuccess 函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-01-18
      • 1970-01-01
      • 1970-01-01
      • 2017-03-03
      • 2019-01-20
      • 2017-04-21
      • 2016-12-12
      相关资源
      最近更新 更多