2016-06-08 3 views
1

Я новичок в использовании promises в nodejs, а также в их тестировании. Мне удалось проверить отдельные модули отдельно, но когда дело доходит до тестирования chain of promises, у меня возникают некоторые проблемы. Я попытался выполнить приведенные примеры here и на странице npm для sinon-as-promised, но, похоже, не удалось управлять потоком и вызвать ошибку в первом promise цепочки.stub nodejs обещание в цепочке вернуть ошибку

Я использую mocha, chai и sinon для моих тестов с sinon-as-promised и chai-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); 
    }); 
}); 
+0

Итак, вы спрашиваете, как вы проверяете несколько операторов обещаний в одном 'it()' и подтверждаете, что обещание должным образом отклоняется с определенной ошибкой? – peteb

+0

Да. Поэтому в моем примере «main» я хочу заставить первое обещание отклонить ошибку с ошибкой и проверить, вызвана ли последняя обратная связь с этой ошибкой. – hyprstack

ответ

0

Прежде чем приступить к тестированию нескольких обещаний и проверке ошибок, проблема с вашим кодом намного больше.

manageShopifyWebhook() создан с использованием анти-шаблона обещаний, который заключается в том, что вы используете структуру обратного вызова, чтобы вернуть свое значение обещания вместо того, чтобы немедленно возвращать свое обещание. Если вы это сделаете, вы уберете большое преимущество обещаний, прямую цепочку для обработки ошибок. Кроме того, вы не сможете использовать sinon-as-promised и chai-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() для проверки Сообщения об ошибке/Error.

Чтобы проверить несколько тестов на обещания, вы можете использовать Promise.all() и передать все свои обещания, возвращающие утверждения, и вернуть результат Promise.all() вашему мокко it().

Я не использую sinon, но приведенное выше должно было дать вам достаточно указания, чтобы выяснить, как использовать этот шаблон с помощью sinon-as-promised, так как он будет работать для любой библиотеки тестирования тестирования Promise.

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

Спасибо peteb. Один вопрос. Если вы возвращаете обещание в 'catch', разве это не должно произойти раньше, чем у вас есть« return result »? – hyprstack

+0

Они оба возвращают обещание, потому что вы возвращаете обещание, созданное 'sWebHook.verify()'. В 'then()' 'результат возврата автоматически разрешается, это означает« успех »или, скорее, отсутствие отказа. Значение, возвращаемое в 'catch()', является отказом, уведомляет 'return Promise.reject()', он сигнализирует вызывающему 'manageShopifyWebhook()', что что-то произошло, и что функция не была успешной. – peteb

+0

Как и сейчас, могу ли я передать ошибку вызывающей стороне 'manageShopifyWebhook()'? – hyprstack

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; 

Секрет заключается в том, чтобы вручную resolve или reject обещании и писать ожидания в пределах обратного вызова функций then или catch методов (как мы это сделали бы, если бы мы писали тесты для асинхронного кода, используя 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); 
    }); 
}); 

Bonus образца - мне нужно, чтобы запустить свой код на 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); 
    }); 
}; 

И для этого мне нужно было контролировать результат обещаний и убедиться, что обратный вызов был вызван либо 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); 
    }); 
    }); 
}); 
Смежные вопросы