Nodejs – 使用MochatestingAWS

我在为使用AWSgraphicsmagick的以下nodejs代码编写testing时遇到了麻烦。 我试图search如何编写asyncwaterfall方法testing的例子,但没有任何明确的结果。

 // dependencies var async = require('async'); var AWS = require('aws-sdk'); var gm = require('gm').subClass({ imageMagick: true }); var util = require('util'); // get reference to S3 client var s3 = new AWS.S3(); exports.AwsHandler = function(event, context) { // Read options from the event. console.log("Reading options from event:\n", util.inspect(event, {depth: 5})); var srcBucket = event.Records[0].s3.bucket.name; var srcKey = event.Records[0].s3.object.key; var dstnKey = srcKey; // Infer the image type. var typeMatch = srcKey.match(/\.([^.]*)$/); if (!typeMatch) { console.error('unable to infer image type for key ' + srcKey); return; } var imageType = typeMatch[1]; if (imageType != "jpg" && imageType != "png") { console.log('skipping non-image ' + srcKey); return; } //Download the image from S3, transform, and upload to same S3 bucket but different folders. async.waterfall([ function download(next) { // Download the image from S3 into a buffer. s3.getObject({ Bucket: srcBucket, Key: srcKey }, next); }, function transformSave(response, next) { var _buffer = null; for (var i = 0; i<len; i++) { // Transform the image buffer in memory. gm(response.Body, srcKey) .resize(_sizesArray[i].width) .toBuffer(imageType, function(err, buffer) { if (err) { next(err); } else { console.log(buffer); _buffer = buffer; } }); // put newly resized image into respective folder s3.putObject({ Bucket: srcBucket, Key: "dst/" + _sizesArray[i].destinationPath + "/" + dstnKey, Body: _buffer, ContentType: response.ContentType }, next); } }, ], function (err) { if (err) { console.error( '---->Unable to resize ' + srcBucket + '/' + srcKey + ' and upload to ' + srcBucket + '/dst' + ' due to an error: ' + err ); } else { console.log( '---->Successfully resized ' + srcBucket + ' and uploaded to ' + srcBucket + "/dst" ); } context.done(); } ); 

}; 到目前为止,我对这个模块的testing:

 require('blanket')({ pattern: function (filename) { return !/node_modules/.test(filename); } }); // in terminal, type the following command to get code coverage: mocha -R html-cov > coverage.html var chai = require('chai'); var sinonChai = require("sinon-chai"); var expect = chai.expect; var sinon = require('sinon'); chai.use(sinonChai); var sync = require("async"); var proxyquire = require('proxyquire'); describe('Image Resizing module', function () { var gmSubclassStub = sinon.stub(); var getObjectStub = sinon.stub(); var putObjectSpy = sinon.spy(); var testedModule = proxyquire('../index', { 'gm': {subClass: sinon.stub().returns(gmSubclassStub)}, 'AWS': { "s3": { getObject: sinon.stub().returns(getObjectStub), putObject: putObjectSpy } } }); describe('AwsHandler', function () { var event = { "Records": [ { "s3": { "bucket": { "name": "testbucket" }, "object": { "key": "test.jpg" } } } ] }; it("should call gm write with correct files", function () { // Arrange // Spies are the methods you expect were actually called var buffer800Spy = sinon.spy(); var buffer500Spy = sinon.spy(); var buffer200Spy = sinon.spy(); var buffer45Spy = sinon.spy(); // This is a stub that will return the correct spy for each iteration of the for loop var resizeStub = sinon.stub(); resizeStub.withArgs(800).returns({toBuffer: buffer800Spy}); resizeStub.withArgs(500).returns({toBuffer: buffer500Spy}); resizeStub.withArgs(200).returns({toBuffer: buffer200Spy}); resizeStub.withArgs(45).returns({toBuffer: buffer45Spy}); // Stub is used when you just want to simulate a returned value var nameStub = sinon.stub().yields({"name": "testbucket"}); var keyStub = sinon.stub().yields({"key": "test.jpg"}); gmSubclassStub.withArgs(event).returns({resize:resizeStub}); getObjectStub.withArgs(event).yields({name: nameStub}, {key: keyStub}); // Act - this calls the tested method testedModule.AwsHandler(event); // Assert }); }); }); 

这里很难回答这个问题。 这个问题不是很具体,也不是一个可以用意见,想法等来回答的公开问题。

因此,我创build了一个类似的实现,解决了async.waterfall问题,并提供了一个testingAwsHandler 100%覆盖率的testing。

代码是在这个要点 ,因为它比那里更方便,更易读。

我还写了一篇与这个实现相关的博文

有几件事情需要改变:

  • 你想testing单元的操作,而不用testing实现。 这就是为什么你应该忽略testing中的asynchronous(就像你一样)。 这只是一种实施方法,单位的内部工作。 你应该testing的是,在给定的条件下,单位给出预期的最终结果,在这种情况下,它调用s3.putObject。 所以你应该保留一切外部的东西(gm和aws),并且监视 s3.putObject方法,因为这是预期的最终结果。

  • 在你的存根中你使用了“yield”,它调用了callback函数,但是只有当它是第一个参数的时候。 如果不是,就像我们的情况一样,您需要使用“callsArgWith(index,…)”作为callback参数的索引。

  • proxyquire必须具有与需要它们的代码完全相同的名称的注入模块 – 将“AWS”更改为“aws-sdk”检查存根是否被正确注入的方法是在debugging器中, “s3”variables,并检查它是“函数代理()”而不是“函数()”。 如果您不使用debugging器,也可以将其打印到控制台。

  • 您的模块正在调用for循环的下一个,这将导致瀑布拆分成36个调用完成(!)的树。 也许你应该使用一个不同的asynchronous模型,如地图缩小。 我通过添加一个愚蠢的条件来修复它,但这不是好的代码。

  • 作为一个便笺,你可以看到testing变得非常复杂。 这可以表明,testing的代码可以使用一些关注的分离。 例如,将gm操作和s3操作移动到两个单独的模块可以帮助分离事物,并且使其更易于testing。

模块本身的变化,防止下次调用4 * 4次:

 function transform(response, next) { for (var i = 0; i<len; i++) { // Transform the image buffer in memory. gm(response.Body, srcKey) .resize(_sizesArray[i].width) .toBuffer(imageType, function(err, buffer) { if (err) { next(err); } else { next(null, response.ContentType, buffer, i); } }); } }, function upload(contentType, data, i, next) { // Stream the transformed image to a different folder. s3.putObject({ Bucket: srcBucket, Key: "dst/" + _sizesArray[i].destinationPath + "/" + dstnKey, Body: data, ContentType: contentType }, function(err) { if (i==3) next(err); }); } 

和testing:

 describe.only('Image Resizing module', function () { var gmSubclassStub = sinon.stub(); var s3Stub = {}; var proxyquire = require('proxyquire'); var testedModule = proxyquire('../index', { 'gm': {subClass: sinon.stub().returns(gmSubclassStub)}, 'aws-sdk': {"S3": sinon.stub().returns(s3Stub)} }); describe('AwsHandler', function () { var event = {}; // The done callback is used for async testing it("should call gm write with correct files", function (done) { // Arrange var resizeStub = sinon.stub(); var buffer800Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "800 buffer"); var buffer500Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "500 buffer"); var buffer200Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "200 buffer"); var buffer45Spy = sinon.stub().withArgs("jpg").callsArgWith(1, null, "45 buffer"); resizeStub.withArgs(800).returns({toBuffer: buffer800Spy}); resizeStub.withArgs(500).returns({toBuffer: buffer500Spy}); resizeStub.withArgs(200).returns({toBuffer: buffer200Spy}); resizeStub.withArgs(45).returns({toBuffer: buffer45Spy}); gmSubclassStub.withArgs("response body", "test.jpg").returns({resize: resizeStub}); s3Stub.getObject = sinon.stub() .withArgs({name: "testbucket", key: "test.jpg"}) .callsArgWith(1, null, { Body: "response body", ContentType: "response content type" }); var putObjectMock = sinon.mock(); s3Stub.putObject = putObjectMock; putObjectMock.callsArgWith(1, null, {}); // return behaviour of the mock putObjectMock.exactly(4); // sets expectation that it is called 4 times // Act - this calls the tested method testedModule.AwsHandler(event, { done: function () { // Assertions need to be inside callback because it is async assert.deepEqual(putObjectMock.getCall(0).args[0], { Bucket: "testbucket", Key: "dst/large/test.jpg", Body: "800 buffer", ContentType: "response content type" }); assert.deepEqual(putObjectMock.getCall(1).args[0], { Bucket: "testbucket", Key: "dst/medium/test.jpg", Body: "500 buffer", ContentType: "response content type" }); assert.deepEqual(putObjectMock.getCall(2).args[0], { Bucket: "testbucket", Key: "dst/small/test.jpg", Body: "200 buffer", ContentType: "response content type" }); assert.deepEqual(putObjectMock.getCall(3).args[0], { Bucket: "testbucket", Key: "dst/thumbnail/test.jpg", Body: "45 buffer", ContentType: "response content type" }); // This ends the async test done(); } }); }); }); });