问题
I am trying to write some test coverage for an app that uses the aws-sdk
NPM module that pushes things up to a SQS queue, but I am unsure how to mock things correctly.
Here is my test so far:
var request = require('superagent'),
expect = require('chai').expect,
assert = require('chai').assert,
sinon = require('sinon'),
AWS = require('aws-sdk'),
app = require("../../../../app");
describe("Activities", function () {
describe("POST /activities", function () {
beforeEach(function(done) {
sinon.stub(AWS.SQS.prototype, 'sendMessage');
done();
});
afterEach(function(done) {
AWS.SQS.prototype.sendMessage.restore();
done();
});
it("should call SQS successfully", function (done) {
var body = {
"custom_activity_node_id" : "1562",
"campaign_id" : "318"
};
reqest
.post('/v1/user/123/custom_activity')
.send(body)
.set('Content-Type', 'application/json')
.end(function(err, res) {
expect(res.status).to.equal(200)
assert(AWS.SQS.sendMessage.calledOnce);
assert(AWS.SQS.sendMessage.calledWith(body));
});
});
});
});
The error I am seeing is:
1) Activities POST /activities "before each" hook:
TypeError: Attempted to wrap undefined property sendMessage as function
2) Activities POST /activities "after each" hook:
TypeError: Cannot call method 'restore' of undefined
I am a bit of a newb when it comes to sinon.stub
or mocking objects in JavaScript, so please excuse my ignorance
回答1:
We have created an aws-sdk-mock npm module which mocks out all the AWS SDK services and methods. https://github.com/dwyl/aws-sdk-mock
It's really easy to use. Just call AWS.mock with the service, method and a stub function.
AWS.mock('SQS', 'sendMessage', function(params, callback) {
callback(null, 'success');
});
Then restore the methods after your tests by calling:
AWS.restore('SQS', 'sendMessage');
回答2:
This is how I stub the AWS-SDK using sinonjs
import AWS from 'aws-sdk'
import sinon from 'sinon'
let sinonSandbox
const beforeEach = (done) => {
sinonSandbox = sinon.sandbox.create()
done()
}
const afterEach = done => {
sinonSandbox.restore()
done()
}
lab.test('test name', (done) => {
sinonSandbox.stub(AWS, 'SQS')
.returns({
getQueueUrl: () => {
return {
QueueUrl: 'https://www.sample.com'
}
}
})
done()
})
Basically I control all the methods from the main SQS. Hope this will help someone
回答3:
You can stub the AWS SDK methods with Sinon with the following
Wrap the AWS SDK instance and allow the this to be set externally:
//Within say, SqsService.js var Aws = require('aws-sdk'); exports.sqsClient = new Aws.SQS({ region: <AWS_REGION>, apiVersion: <API_VERSION>, accessKeyId: <AWS_ACCESS_KEY_ID>, secretAccessKey: <AWS_SECRET_KEY> });
When using the
sqsClient
, ensure to use the wrapped instance instead.var SqsService = require('./SqsService'); function (message, callback) { //Do stuff.. //Then send stuff.. SqsService.sqsClient.sendMessage(message, callback); });
So modifying your test case from above, using the wrapped AWS SDK:
var request = require('superagent'), expect = require('chai').expect, assert = require('chai').assert, sinon = require('sinon'), SqsService = require('./SqsService'), //Import wrapper app = require("../../../../app"); describe("Activities", function () { describe("POST /activities", function () { var sendMessageStub; beforeEach(function(done) { //Stub like so here sendMessageStub = sinon.stub(SqsService.sqsClient, 'sendMessage').callsArgWith(1, null, { MessageId: 'Your desired MessageId' }); done(); }); afterEach(function(done) { sendMessageStub.restore(); done(); }); it("should call SQS successfully", function (done) { var body = { "custom_activity_node_id" : "1562", "campaign_id" : "318" }; reqest .post('/v1/user/123/custom_activity') .send(body) .set('Content-Type', 'application/json') .end(function(err, res) { expect(res.status).to.equal(200) assert(sendMessageStub.calledOnce); assert(sendMessageStub.calledWith(body)); }); }); }); });
回答4:
You can do it without bringing in any extra libraries using something like this:
const mocha = require('mocha'),
chai = require('chai'),
expect = chai.expect, // Using Expect style
sinon = require('sinon'),
AWS = require('aws-sdk');
describe('app', function () {
var aws, sqs, app,
sendMessageError = null,
sendMessageData = { MessageId: "1" };
before(() => {
// Create a stub for the SQS lib
sqs = sinon.stub({ sendMessage: Function() });
// Make sure that when someone calls AWS.SQS they get our stub
aws = sinon.stub(AWS, 'SQS');
aws.returns(sqs);
// Now include your app since it will `require` our stubbed version of AWS
app = require('./app');
});
after(() => {
aws.restore(); // Be kind to future tests
});
beforeEach(() => {
// Reset callback behavior after each test
sqs.sendMessage.reset();
// Call the callback supplied to sendMessage in the 1st position with the arguments supplied
sqs.sendMessage.callsArgWith(1, sendMessageError, sendMessageData);
});
it('sends messages', () => {
// Pretend you're using Promises in your app, but callbacks are just as easy
return app.sendMessage().then(() => {
const args = sqs.sendMessage.getCall(0).args[0];
expect(args.QueueUrl).to.be.eq('http://127.0.0.1/your/queue/url');
});
});
});
回答5:
I think the issue is that AWS SDK classes are built dynamically from JSON configuration. Here's the one for SQS: Github.
All API calls eventually make it down to makeRequest
or makeUnauthenticatedRequest
on Service, so I just stubbed those using withArgs(...)
. For example:
var stub = sinon.stub(AWS.Service.prototype, 'makeRequest');
stub.withArgs('assumeRole', sinon.match.any, sinon.match.any)
.yields(null, fakeCredentials);
which worked fine for my simple use case.
回答6:
I can't tell you exactly why it is not possible with Sinon to stub the aws sdk (maybe some JS expert can explain that better) but it works with proxyquire very well.
Proxies nodejs's require in order to make overriding dependencies during testing easy while staying totally unobstrusive.
回答7:
I like to use promises, building upon @kdlcruz's answer above, I do something like this:
import AWS from 'aws-sdk'
import sinon from 'sinon'
let sinonSandbox
const beforeEach = (done) => {
sinonSandbox = sinon.sandbox.create()
done()
}
const afterEach = done => {
sinonSandbox.restore()
done()
}
function mockAWSCall(service, method, expectedArgs, response) {
var stubDef = {};
stubDef[method] = function(args) {
if(expectedArgs) {
expect(args).to.deep.equal(expectedArgs);
}
return {
promise: () => {
return new Promise(function (resolve, reject) {
if(response.startsWith("ERROR:")) {
reject(response);
} else {
resolve(response);
}
});
}
};
};
sinonSandbox.stub(AWS, service).returns(stubDef);
}
lab.test('test name', (done) => {
mockAWSCall('SQS', 'sendMessage', {
MessageBody: 'foo', QueueUrl: 'http://xxx'
}, 'ok');
// Do something that triggers the call...
done()
})
来源:https://stackoverflow.com/questions/26243647/sinon-stub-in-node-with-aws-sdk