存根nodejs承诺在链中返回错误

nodejs使用nodejs中的nodejs ,也是在testing它们。 我已经单独testing了各个模块,但是当涉及testingchain of promises ,我遇到了一些麻烦。 我尝试了下面的例子,在npm页面上find了sinon-as-promised的例子,但似乎没有设法控制stream程并触发链中第一个promise的错误。

我正在使用mochachaisinon for作为我sinon的testing, sinon-as-promisedchai-as-promised

我试图testing这个模块:

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

这是我想在mainstream程中触发rejectcallback的sWebHook模块:

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

到目前为止,这些都是我的testing(不起作用):

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

所以,我最终搞清楚了如何使用sinontesting承诺链。 对于下面的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方法的callback函数内写入期望值(就像我们使用done编写asynchronous代码testing一样)。 然后我们触发我们想要testing的方法,将它的值保存到一个variables中。 像这样:

 '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上运行我的代码,因此需要进行最后的callback。 所以我有一个名为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); }); }; 

为此,我需要控制承诺的结果,并确保callback调用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); }); }); }); 

在了解如何testing多个承诺并validation错误之前,您的代码有一个更大的问题。

manageShopifyWebhook()是使用承诺的反模式构造的,即使用callback结构来返回承诺值,而不是直接返回承诺。 如果你这样做的话,你将带走一大笔承诺,直接链接error handling。 此外,你将无法使用sinon-as-promisedchai-as-promised因为他们期望一个Promise / thenable返回。

但是,通过简单地返回由sWebHook.verify()创build的承诺,在代码中可以快速修复:

 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-promisedtesting库。

对于chai-as-promised您需要将expect()转换为eventually使用链表,然后使用rejectedWith()validation错误/错误消息。

为了validation多个promise的testing,你可以使用Promise.all()并传递你所有的promise返回assertions并把Promise.all()的结果返回给你的mocha it()

我不使用sinon但是上面应该给你足够的方向来弄清楚如何用sinon-as-promised来使用这个模式,因为它将适用于任何Promise返回testing库。

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