存根nodejs承诺在链中返回错误
我nodejs
使用nodejs
中的nodejs
,也是在testing它们。 我已经单独testing了各个模块,但是当涉及testingchain of promises
,我遇到了一些麻烦。 我尝试了下面的例子,在npm
页面上find了sinon-as-promised的例子,但似乎没有设法控制stream程并触发链中第一个promise
的错误。
我正在使用mocha
, chai
和sinon
for作为我sinon
的testing, sinon-as-promised
和chai-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;
这是我想在main
stream程中触发reject
callback的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); }); });
所以,我最终搞清楚了如何使用sinon
testing承诺链。 对于下面的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;
秘密是手动resolve
或reject
承诺,并在then
或catch
方法的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调用error
或success
消息。 前提是一样的。
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-promised
和chai-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-promised
testing库。
对于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); ]); });