stub nodejs在链中承诺返回错误

时间:2016-06-08 13:54:16

标签: node.js sinon chai es6-promise

我不熟悉在promises中使用nodejs以及测试它们。我已经设法分别测试了各个模块,但是在测试chain of promises时,我遇到了一些麻烦。我尝试按照heresinon-as-promised以及https://forums.xamarin.com/discussion/30801/xamarin-forms-bindable-pickernpm页面上的示例进行操作,但似乎无法控制流量并在第一个promise中触发错误连锁,链条。

我正在使用mochachaisinon进行sinon-as-promisedchai-as-promised的测试。

我正在尝试测试这个模块:

'use strict';
var mySQS = require('./modules/sqs/sqs-manager');
var sWebHook = require('./modules/webhooks/shopify/webhooks');
var main = {};
main.manageShopifyWebhook = function (params, callback) {
  sWebHook.verify(params.srcHmac, params.rawBody, params.shopName.split('.myshopify.com')[0], params.productId)
    .then(function(data) {
      var body = {
        "params": {
          "productId": data.productId,
          "shopName": data.shopName
        },
        "job": "call-update-item"
      };
      mySQS.create_Queue(body)
        .then(mySQS.send_Message)
        .then(function(result) {
          callback(null, result);
        })
        .catch(function(error) {
          callback(error, null);
        });
    });
};

module.exports = main;

这是我要在sWebHook流程中触发reject回调的main模块:

'use strict';

var crypto = require('crypto');
var nconf = require('../../../../config/nconfig');

var webHookManager = {};

webHookManager.verify = function (srcHmac, rawBody, shopName, productId) {
  return new Promise(function (resolve, reject) {
    rawBody = new Buffer(rawBody, 'base64');
    var sharedSecret = nconf.get('SHOPIFY_CLIENT_SECRET');
    var digest = crypto.createHmac('SHA256', sharedSecret).update(rawBody).digest('base64');
    console.log('***** CALCULATED DIGEST *****');
    console.log(digest);
    console.log('***** HMAC FROM SHOPIFY *****');
    console.log(srcHmac);
    if (digest !== srcHmac) {
      console.log('Hello');
      var customError = new Error('Unauthorized: HMAC Not Verified');
      reject(customError);
      return false;
    }
    var newEvent = {
      shopName: shopName,
      productId: productId
    };
    console.log('!! WEBHOOK VERIFIED !!');
    resolve(newEvent);
  });
};

module.exports = webHookManager;

到目前为止这些是我的测试(不起作用):

'use strict';

var chai = require('chai');
var sinonChai = require("sinon-chai");
var expect = chai.expect;
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var sinon = require('sinon');
chai.use(sinonChai);
var proxyquire = require('proxyquire').noCallThru();
var AWS = require('mock-aws');

describe('MAIN', function() {
  require('sinon-as-promised');
  var testedModule,
    sWebHookStub,
    sqsQueueStub,
    sqsSendMsgStub,
    callbackSpy,
    fakeDataObj;

  before(function() {
    sWebHookStub = sinon.stub();
    sqsQueueStub = sinon.stub();
    sqsSendMsgStub = sinon.stub();
    callbackSpy = sinon.spy();
    fakeDataObj = {
      srcHmac: '12345',
      rawBody: 'helloworld',
      shopName: 'mario-test.myshopify.com',
      productId: '6789'
    };
    testedModule = proxyquire('../lib/main', {
      './modules/webhooks/shopify/webhooks': {
        'verify': sWebHookStub
      },
      './modules/sqs/sqs-manager': {
        'create_Queue': sqsQueueStub,
        'send_Message': sqsSendMsgStub
      }
    });
  });

  it('calling shopifyVeriWebhook returns an error', function() {
    var fakeError = new Error('Error verifying webhook');
    sWebHookStub.rejects(fakeError);

    testedModule.manageShopifyWebhook(fakeDataObj, function() {
      callbackSpy.apply(null, arguments);
    });
    expect(callbackSpy).has.been.called.and.calledWith(fakeError, null);
  });
});

2 个答案:

答案 0 :(得分:1)

所以,我最终弄清楚如何使用sinon来测试承诺链。对于以下main模块(注意:其他模块都返回承诺):

'use strict';

var mySQS = require('./modules/sqs/sqs-manager');
var sWebHook = require('./modules/webhooks/shopify/webhooks');

var main = {};

//@params {object} params
//@params {string} params.srcHmac
//@params {string} params.rawBody
//@params {string} params.shopName - <shop-name.myshopify.com>
//@params {string} params.productId

main.manageShopifyWebhook = function (params) {
  return new Promise(function(resolve, reject) {
    sWebHook.verify(params.srcHmac, params.rawBody, params.shopName.split('.myshopify.com')[0], params.productId)
      .then(function(data) {
        var body = {
          "params": {
            "productId": data.productId,
            "shopName": data.shopName
          },
          "job": "call-update-item"
        };
        return mySQS.create_Queue(body);
      })
      .then(mySQS.send_Message)
      .then(resolve)
      .catch(function(err) {
        reject(err);
      });
  });
};

module.exports = main;

秘诀是手动resolvereject承诺,并在thencatch方法的回调函数中写下期望(正如我们所做的那样)我们正在使用done)编写异步代码测试。然后我们触发我们想要测试的方法,将其值保存到变量中。像这样:

'use strict';

var chai = require('chai');
var sinonChai = require("sinon-chai");
var expect = chai.expect;
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
require('sinon-as-promised');
var sinon = require('sinon');
chai.use(sinonChai);
var proxyquire = require('proxyquire').noCallThru();

describe('MAIN', function() {
  require('sinon-as-promised');
  var testedModule,
    sWebHookStub,
    sqsQueueStub,
    sqsSendMsgStub,
    callbackSpy,
    fakeDataObj;

  before(function() {
    sWebHookStub = sinon.stub();
    sqsQueueStub = sinon.stub();
    sqsSendMsgStub = sinon.stub();
    callbackSpy = sinon.spy();
    fakeDataObj = {
      srcHmac: '12345',
      rawBody: 'helloworld',
      shopName: 'mario-test.myshopify.com',
      productId: '6789'
    };
    testedModule = proxyquire('../lib/main', {
      './modules/webhooks/shopify/webhooks': {
        'verify': sWebHookStub
      },
      './modules/sqs/sqs-manager': {
        'create_Queue': sqsQueueStub,
        'send_Message': sqsSendMsgStub
      }
    });
  });

  it('calling shopifyVeriWebhook returns an error when trying to VERIFY WEBHOOK', function() {
    var fakeError = new Error('Error verifying webhook');
    sWebHookStub.rejects(fakeError)().catch(function(error) {
      expect(shopifyWebhook).to.eventually.equal(error);
    });
    var shopifyWebhook = testedModule.manageShopifyWebhook(fakeDataObj);
  });

  it('calling shopifyVeriWebhook returns an error when trying to CREATE SQS QUEUE', function() {
    var fakeBody = {
      "params": {
        "productId": '1234',
        "shopName": 'name'
      },
      "job": "call-update-item"
    };
    var fakeError = new Error('Error creating sqs queue');
    sWebHookStub.resolves(fakeBody)().then(function(result) {
      sqsQueueStub.rejects(fakeError)().catch(function(error) {
        expect(shopifyWebhook).to.eventually.equal(error);
      });
    });
    var shopifyWebhook = testedModule.manageShopifyWebhook(fakeDataObj);
  });

  it('calling shopifyVeriWebhook returns an error when trying to SEND SQS MESSAGE', function() {
    var fakeData = {
      queueUrl: '5678',
      payLoad: '{"message": "Hello World"'
    };
    var fakeBody = {
      "params": {
        "productId": '1234',
        "shopName": 'name'
      },
      "job": "call-update-item"
    };
    var fakeError = new Error('Error sending sqs message');
    sWebHookStub.resolves(fakeBody)().then(function(result) {
      sqsQueueStub.resolves(fakeData)().then(function(result) {
        sqsSendMsgStub.rejects(fakeError)().catch(function(error) {
          expect(shopifyWebhook).to.eventually.equal(error);
        });
      });
    });
    var shopifyWebhook = testedModule.manageShopifyWebhook(fakeDataObj);
  });

  it('calling shopifyVeriWebhook is SUCCESSFUL', function() {
    var fakeData = {
      queueUrl: '5678',
      payLoad: '{"message": "Hello World"'
    };
    var fakeBody = {
      "params": {
        "productId": '1234',
        "shopName": 'name'
      },
      "job": "call-update-item"
    };
    var fakeResponse = {
      'message': 'success'
    };
    sWebHookStub.resolves(fakeBody)().then(function(result) {
      sqsQueueStub.resolves(fakeData)().then(function(result) {
        sqsSendMsgStub.resolves(fakeResponse)().then(function(result) {
          expect(shopifyWebhook).to.eventually.equal(result);
        });
      });
    });
    var shopifyWebhook = testedModule.manageShopifyWebhook(fakeDataObj);
  });
});

奖金示例 - 我需要在aws lambda上运行我的代码,因此需要进行最终回调。所以我在名为lambda.js的文件中有我的代码的主要入口点:

'use strict';

var main = require('./lib/main');

//Verifies shopify webhooks
//@params {object} event
//@params {string} event.srcHmac
//@params {string} event.rawBody
//@params {string} event.shopName - <shop-name.myshopify.com>
//@params {string} event.productId
exports.shopifyVerifyWebHook = function (event, context, callback) {
  console.log('---- EVENT ----');
  console.log(event);
  main.manageShopifyWebhook(event)
    .then(function(result) {
      callback(null, result);
    })
    .catch(function(err) {
      callback(err, null);
    });
};

为此,我需要控制pr​​omises的结果,并确保使用errorsuccess消息调用回调。 前提是一样的。

describe('LAMBDA', function() {
  var testedModule,
    mainShopStub,
    callbackSpy,
    mainModule,
    fakeEvent;

  before(function() {
    callbackSpy = sinon.spy();
    fakeEvent = {
      srcHmac: '12345',
      rawBody: 'helloworld',
      shopName: 'mario-test.myshopify.com',
      productId: '6789'
    };
    testedModule = require('../lambda');
    mainModule = require('../lib/main');
    mainShopStub = sinon.stub(mainModule, 'manageShopifyWebhook');
  });

  after(function() {
    mainShopStub.restore();
  });

  it('calling shopifyVerifyWebHook returns an error', function() {
    var fakeError = new Error('Error running lambda');
    mainShopStub.rejects(fakeError);
    mainShopStub().catch(function (error) {
      expect(callbackSpy).has.been.called.and.calledWith(error, null);
    });

    testedModule.shopifyVerifyWebHook(fakeEvent, {}, function() {
      callbackSpy.apply(null, arguments);
    });
  });

  it('calling shopifyVerifyWebHook return a data object', function() {
    var fakeObj = {message: 'success'};
    mainShopStub.resolves(fakeObj);
    mainShopStub().then(function (result) {
      expect(callbackSpy).has.been.called.and.calledWith(null, result);
    });

    testedModule.shopifyVerifyWebHook(fakeEvent, {}, function() {
      expected.resolves(fakeObj);
      callbackSpy.apply(null, arguments);
    });
  });
});

答案 1 :(得分:0)

在开始测试多个承诺和验证错误之前,您的代码存在更大的问题。

manageShopifyWebhook()是使用反模式的承诺构建的,您可以使用回调结构返回您的承诺值,而不是直接返回您的承诺。如果你这样做,你就会带走承诺的大量好处,直接用于错误处理。此外,您无法使用sinon-as-promisedchai-as-promised,因为他们希望返回Promise / thenable

但是,只需返回sWebHook.verify()创建的承诺,即可快速修复代码:

main.manageShopifyWebhook = function (params) {
  // Return the promise directly
  // the final return will be returned to the original caller of manageShopifyWebhook
  return sWebHook.verify(params.srcHmac, params.rawBody, params.shopName.split('.myshopify.com')[0], params.productId)
    .then(function(data) {
      var body = {
        "params": {
          "productId": data.productId,
          "shopName": data.shopName
        },
        "job": "call-update-item"
      };
      return mySQS.create_Queue(body);
    })
      .then(mySQS.send_Message)
      .then(function(result) {
        return result;
      })
      .catch(function(err) {
        // In reality you can let error propagate out here 
        // if you don't need to do anything special with it and let 
        // the promise just return the error directly
        // I've only done this so we can return 'Error Verifying Webhook' as an error from the promise returned by manageShopifyWebhook()
        return Promise.reject(new Error('Error verifying webook'));      
      });
  });
};

现在manageShopfiyWebhook()正在返回一个承诺,您可以使用两个as-promised测试库。

对于chai-as-promised,您需要转换expect()以使用链eventually查找承诺,然后您可以使用rejectedWith()来验证错误/错误消息。

要验证多个promises测试,您可以使用Promise.all()并传递所有承诺,返回断言并将Promise.all()的结果返回到您的mocha it()

我没有使用sinon,但上面应该给你足够的指导,以了解如何将这种模式与sinon-as-promised一起使用,因为它适用于任何承诺返回测试库。

it('calling shopifyVeriWebhook returns an error', function() {
  var fakeError = new Error('Error verifying webhook');
  let shopifyWebhook = testedModule.manageShopifyWebhook(fakeDataObj);

  return Promise.all([
    expect(shopifyWebhook).to.eventually.be.rejectedWith(fakeError);
  ]);
});