使用第三方 API 调用测试异步 redux

Testing async redux with third party API calls

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

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

我已经将外部 API 调用包装在一个 promise 函数中,并且我从 'actual' 动作创建者调用了这个函数。因此,为了进行测试,我只想存根 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 的第二个参数甚至不是普通回调,而是带有回调值的 object 这一事实,情况变得更加复杂。

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

编辑:我正在 Jest 中测试。

编辑 2:请求 Headers First POST request, Second POST request

Edit3:拆分功能,尽我所能隔离外部API并创建'easily mock/stub-able'的东西。但仍然 运行 研究如何正确存根这个函数的问题。

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'});
    })
});

所以我最后想通了。 首先,我必须将模块 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 函数。