使用第三方API调用测试async redux

时间:2016-12-22 11:28:39

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

我对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 requestSecond POST request

编辑3:拆分该功能,尽我所能隔离外部API并创建一些“易于模拟/存根”的功能。但是仍然遇到如何正确存根这个功能的问题。

2 个答案:

答案 0 :(得分:0)

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

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

测试还应该查看哪些参数传递给您的操作创建者,以便您的Reducer可以被告知请求的结果并相应地更新商店。

要开始测试您的thunk action creator,您要测试是否根据登录是否成功来适当地调度这三个操作。

  1. requestLogin
  2. receiveLogin
  3. failedLogin
  4. 以下是我为您开始使用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'});
        })
    });
    

答案 1 :(得分:0)

所以我最终弄明白了。 首先,我必须将()模块放入我的测试文件中(而不是ES6导入)。然后我删除了现在的承诺,因为它增加了一层复杂性并将所有内容组合成一个函数,让我们称之为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函数了。