I'm new to sinon and can't achieve the results I want.
I'm trying to stub AWS S3 API getObject to return a test-provided object.
My production code has:
let s3 = new AWS.S3({ apiVersion: '2006-03-01' });
let params = {
Bucket: aws_bucket,
Key: path
};
s3.getObject(params, function(err, data) {
...
});
My test code has:
describe('GET /image', function() {
beforeEach(function() {
let stub = sinon.createStubInstance(AWS.S3);
stub.getObject.callsFake(() => { console.log('stubbed'); });
});
The AWS S3 class instance is fully stubbed when I run the test, which is great, but it is not calling my fake.
What am I missing?
I found a working approach.
Step 1 Wrap AWS.S3 instance in another module.
// s3.js
var AWS = require('aws-sdk');
var s3 = new AWS.S3({ apiVersion: '2006-03-01' });
module.exports = s3;
Step 2 Change production code to use this instance instead of making its own.
// image.js
var s3 = require('./s3');
// ... other code ...
s3.getObject(...);
Step 3 Stub what needs to be stubbed.
// image-test.js
var s3 = require('./s3');
var getObjectStub;
describe('GET /image', function() {
beforeEach(function() {
getObjectStub = sinon.stub(s3, 'getObject').callsFake(() => { console.log('stubbed'); });
});
afterEach(() => {
getObjectStub.restore();
});
// test code continues
});
Related
I am trying to mock the S3 class inside the aws-sdk module while macking sure that the methods inside the class S3 can be spyed.
I am able to mock the S3 class inside aws-sdk however I cannot spy the methods inside the class.
Any ideas on how to approach this problem?
These are my code snippets:
services/s3.js
const AWS = require('aws-sdk');
const uploadAsset = async (param) => {
try {
const response = await s3.upload(param).promise();
return response;
} catch (e) {
console.log(e);
}
}
module.exports = { uploadAsset }
services.s3.test.js
const AWS = require('aws-sdk');
const { uploadAsset } = require('../services/s3')
jest.mock('aws-sdk', () => {
return {
S3: class {
constructor() { }
upload(param) { // 👈 I want to make sure that this method is called
return {
promise: () => {
return Promise.resolve(
{
Location: `http://${param.Bucket}.s3.amazonaws.com/${param.Key}`,
Key: param.Key
}
)
}
}
}
}
}
});
describe('uploadAsset() functionality', () => {
it('should upload an asset', async () => {
const uploadPath = 'users/profilePicture';
const base64Str = '/9j/4AAQSkZJRgABAQAAAQABAAD/';
const buffer = Buffer.from(base64Str, 'base64');
const s3 = new AWS.S3();
const response = await uploadAsset({
Bucket: 'BucketName,
Key: `KeyName`,
Body: buffer,
});
const spy = jest.spyOn(s3, 'deleteObject')
expect(spy).toBeCalled(); // 🚨 This spy nevers gets called
});
});
Any insights would be helpful.
Thanks.
I mocked the aws-sdk successfully. However my spy in the S3 never gets called.
I am almost positive that this is a scope problem. I think my spyOn method only affects my local S3 class instance. However I still have no idea how to test this specific case scenario.
I want to extract text from image using node js so created a lambda in aws. Please find the below code snippet. Issue is that the textract method detectDocumentText is not getting invoked.
As far as permission I had given s3 full access and textract full access to the lambda. Am I missing anything?
var AWS = require("aws-sdk");
var base64 = require("base-64");
var fs = require("fs");
exports.handler = async (event, context, callback) => {
// Input for textract can be byte array or S3 object
AWS.config.region = "us-east-1";
//AWS.config.update({ region: 'us-east-1' });
var textract = new AWS.Textract({ apiVersion: "2018-06-27" });
//var textract = new AWS.Textract();
console.log(textract);
var params = {
Document: {
/* required */
//'Bytes': imageBase64
S3Object: {
Bucket: "717577",
Name: "Picture2.png"
}
}
};
textract.detectDocumentText(params, function(err, data) {
if (err) {
console.log(err); // an error occurred
} else {
console.log(data); // successful response
callback(null, data);
}
});
};
As well as I don't see any error logs in cloudwatch logs.
The problem is that you have marked your method as async which means that you are returning a promise. In your case you are not returning a promise so for lambda there is no way to complete the execution of the method. You have two choices here
Remove async
Or more recommended way is to convert your callback style to use promise. aws-sdk support .promise method on all methods so you could leverage that. The code will look like this
var AWS = require("aws-sdk");
var base64 = require("base-64");
var fs = require("fs");
exports.handler = async (event, context) => {
// Input for textract can be byte array or S3 object
AWS.config.region = "us-east-1";
//AWS.config.update({ region: 'us-east-1' });
var textract = new AWS.Textract({ apiVersion: "2018-06-27" });
//var textract = new AWS.Textract();
console.log(textract);
var params = {
Document: {
/* required */
//'Bytes': imageBase64
S3Object: {
Bucket: "717577",
Name: "Picture2.png"
}
}
};
const data = await textract.detectDocumentText(params).promise();
return data;
};
Hope this helps.
I'm trying to mock SES with Sinon, but facing below error. Tried using aws-sdk-mock, but it's not working.
Error: TypeError: Cannot stub non-existent own property sendEmail
Code snippet of test class:
import * as AWS from 'aws-sdk';
const sandbox = sinon.createSandbox();
sandbox.stub(AWS.SES, 'sendEmail').returns({promise: () => true});
Actual class:
import * as AWS from 'aws-sdk';
import * as _ from 'lodash';
export async function sendAlertMailOnFailure(status:any)
{
// load AWS SES
var ses = new AWS.SES();
const params = {
Destination: {
ToAddresses: <to_address>
},
Message: {...},
Source: <sender_address>
}
ses.sendEmail(params, (err, data) => {
if (err) {
log.error("Error sending mail::");
log.error(err, err.stack);
}
})
}
Is there any way to mock SES with Sinon or with aws-sdk-mock?
My answer here is not a direct solution for SES, but it is a working solution I'm using for mocking DynamoDB.DocumentClient and SQS. Perhaps you can adapt my working example for SES and other aws-sdk clients in your unit tests.
I just spent hours trying to get AWS SQS mocking working, without resorting to the aws-sdk-mock requirement of importing aws-sdk clients inside a function.
The mocking for AWS.DynamoDB.DocumentClient was pretty easy, but the AWS.SQS mocking had me stumped until I came across the suggestion to use rewire.
My lambda moves bad messages to a SQS FailQueue (rather than letting the Lambda fail and return the message to the regular Queue for retries, and then DeadLetterQueue after maxRetries). The unit tests needed to mock the following SQS methods:
SQS.getQueueUrl
SQS.sendMessage
SQS.deleteMessage
I'll try to keep this example code as concise as I can while still including all the relevant parts:
Snippet of my AWS Lambda (index.js):
const AWS = require('aws-sdk');
AWS.config.update({region:'eu-west-1'});
const docClient = new AWS.DynamoDB.DocumentClient();
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
// ...snip
Abridged Lambda event records (event.json)
{
"valid": {
"Records": [{
"messageId": "c292410d-3b27-49ae-8e1f-0eb155f0710b",
"receiptHandle": "AQEBz5JUoLYsn4dstTAxP7/IF9+T1S994n3FLkMvMmAh1Ut/Elpc0tbNZSaCPYDvP+mBBecVWmAM88SgW7iI8T65Blz3cXshP3keWzCgLCnmkwGvDHBYFVccm93yuMe0i5W02jX0s1LJuNVYI1aVtyz19IbzlVksp+z2RxAX6zMhcTy3VzusIZ6aDORW6yYppIYtKuB2G4Ftf8SE4XPzXo5RCdYirja1aMuh9DluEtSIW+lgDQcHbhIZeJx0eC09KQGJSF2uKk2BqTGvQrknw0EvjNEl6Jv56lWKyFT78K3TLBy2XdGFKQTsSALBNtlwFd8ZzcJoMaUFpbJVkzuLDST1y4nKQi7MK58JMsZ4ujZJnYvKFvgtc6YfWgsEuV0QSL9U5FradtXg4EnaBOnGVTFrbE18DoEuvUUiO7ZQPO9auS4=",
"body": "{ \"key1\": \"value 1\", \"key2\": \"value 2\", \"key3\": \"value 3\", \"key4\": \"value 4\", \"key5\": \"value 5\" }",
"attributes": {
"ApproximateReceiveCount": "1",
"SentTimestamp": "1536763724607",
"SenderId": "AROAJAAXYIAN46PWMV46S:steve.goossens#bbc.co.uk",
"ApproximateFirstReceiveTimestamp": "1536763724618"
},
"messageAttributes": {},
"md5OfBody": "e5b16f3a468e6547785a3454cfb33293",
"eventSource": "aws:sqs",
"eventSourceARN": "arn:aws:sqs:eu-west-1:123456789012:sqs-queue-name",
"awsRegion": "eu-west-1"
}]
}
}
Abridged unit test file (test/index.test.js):
const AWS = require('aws-sdk');
const expect = require('chai').expect;
const LamdbaTester = require('lambda-tester');
const rewire = require('rewire');
const sinon = require('sinon');
const event = require('./event');
const lambda = rewire('../index');
let sinonSandbox;
function mockGoodSqsMove() {
const promiseStubSqs = sinonSandbox.stub().resolves({});
const sqsMock = {
getQueueUrl: () => ({ promise: sinonSandbox.stub().resolves({ QueueUrl: 'queue-url' }) }),
sendMessage: () => ({ promise: promiseStubSqs }),
deleteMessage: () => ({ promise: promiseStubSqs })
}
lambda.__set__('sqs', sqsMock);
}
describe('handler', function () {
beforeEach(() => {
sinonSandbox = sinon.createSandbox();
});
afterEach(() => {
sinonSandbox.restore();
});
describe('when SQS message is in dedupe cache', function () {
beforeEach(() => {
// mock SQS
mockGoodSqsMove();
// mock DynamoDBClient
const promiseStub = sinonSandbox.stub().resolves({'Item': 'something'});
sinonSandbox.stub(AWS.DynamoDB.DocumentClient.prototype, 'get').returns({ promise: promiseStub });
});
it('should return an error for a duplicate message', function () {
return LamdbaTester(lambda.handler)
.event(event.valid)
.expectReject((err, additional) => {
expect(err).to.have.property('message', 'Duplicate message: {"Item":"something"}');
});
});
});
});
You need to use prototype in AWS to stub it:
import AWS from 'aws-sdk';
const sandbox = sinon.createSandbox();
sandbox.stub(AWS.prototype, 'SES').returns({
sendEmail: () => {
return true;
}
});
The error seems to indicate that AWS is being imported as undefined.
It might be that your ES6 compiler isn't automatically turning this line:
import AWS from 'aws-sdk';
...into an import of everything in aws-sdk into AWS.
Change it to this:
import * as AWS from 'aws-sdk';
...and that may fix the issue.
(Disclaimer: I can't reproduce the error in my environment which is compiling with Babel v7 and automatically handles either approach)
Using require & without using prototype. This is working for me for mocking DynamoDB.
const aws = require('aws-sdk');
const sinon = require('sinon');
const sandbox = sinon.createSandbox();
this.awsStub = sandbox.stub(aws, 'DynamoDB').returns({
query: function() {
return {
promise: function() {
return {
Items: []
};
}
};
}
});
Packages:
"aws-sdk": "^2.453.0"
"sinon": "^7.3.2"
I was able to use awk-sdk-mock by doing the following:
test class
const AWSMock = require('aws-sdk-mock');
const AWS = require('aws-sdk');
AWSMock.setSDKInstance(AWS);
...
AWSMock.mock('SES', 'sendRawEmail', mockSendEmail);
// call method that needs to mock send an email goes below
sendEmail(to, from, subject, body, callback);
function mockSendEmail(params, callback) {
console.log('mock email');
return callback({
MessageId: '1234567',
});
}
Actual class
const aws = require('aws-sdk');
const nodemailer = require('nodemailer');
function sendEmail(to, from, subject, body, callback) {
let addresses = to;
if (!Array.isArray(addresses)) {
addresses = [addresses];
}
let replyTo = [];
if (from) {
replyTo.push(from);
}
let data = {
to: addresses,
replyTo,
subject,
text: body,
};
nodemailer.createTransport({ SES: new aws.SES({ apiVersion: '2010-12-01' }) }).sendMail(data, callback);
}
const AWS = require('aws-sdk');
...
const sandbox = sinon.createSandbox();
sandbox.stub(AWS, 'SES').returns({
sendRawEmail: () => {
console.log("My sendRawEmail");
return {
promise: function () {
return {
MessageId: '987654321'
};
}
};
}
});
let ses = new AWS.SES({ region: 'us-east-1' });
let result = ses.sendRawEmail(params).promise();
I have been having a problem trying to make sure Q.ninvoke is called with the args I am passing in. I am new to testing with Sinon, Mocha and Chai. I have been trying everything I have found online for 2 days now and I still cant get my test pass. What am I doing wrong?
This is my code under test.
var cuid = require('cuid');
var fs = require('fs');
var Q = require('q');
var AWS = require('aws-sdk');
var S3 = new AWS.S3();
module.exports = {
initialize: initialize
};
function initialize(options) {
return Q.nfcall(fs.readFile, options.path).then(function (file) {
var fileParams = {
Bucket: options.bucket,
Key: options.name,
Body: file,
ContentType: options.contentType
};
return Q.ninvoke(S3, 'upload', fileParams).then(function(data){
return data.Location;
});
});
}
Here is my test.
describe.only('when a file is read successfully', function() {
var spy;
beforeEach(function() {
spy = chai.spy.on(Q, 'ninvoke');
sinon.stub(Q, 'nfcall').withArgs(fs.readFile, fileParams.path).returns(Q.resolve(file));
});
it('Q.ninvoke should be called with args', function() {
UploadCommand.initialize(fileParams)
expect(spy).to.have.been.called.with(S3, 'upload', params);
});
});
This is the error I am getting.
1) UploadCommand .initialize when a file is read successfully
Q.ninvoke should be called with args:
AssertionError: expected { Spy } to have been called with [ Array(3) ]
try this:
var cuid = require('cuid');
var fs = require('fs');
var Q = require('q');
var AWS = require('aws-sdk');
var S3 = new AWS.S3();
module.exports = {
initialize: initialize
};
function initialize(options) {
return Q.nfcall(fs.readFile, options.path).then(function (file) {
var fileParams = {
Bucket: options.bucket,
Key: options.name,
Body: file,
ContentType: options.contentType
};
return Q.ninvoke(S3, 'upload', fileParams);
});
}
note in particular that you should return a promise from your initialize function. then in the test:
describe.only('when a file is read successfully', function() {
var spy;
beforeEach(function() {
spy = chai.spy.on(Q, 'ninvoke');
sinon.stub(Q, 'nfcall').withArgs(fs.readFile,fileParams.path).returns(Q.resolve(file));
});
it('Q.ninvoke should be called with args', function(done) {
UploadCommand.initialize(fileParams).then(function(data) {
expect(spy).to.have.been.called.with(S3, 'upload', params);
done();
});
});
});
a couple of other things to note, in your main application code, you will also want to chain your initialize function to a 'then' function, and in the body of that then function is where the rest of your application code should go. also, the 'done' callback is the way you tell mocha that it is an asynchronous test.
Mike I was able to get it working finally thanks to you. I really appreciate it! Here is the final test.
describe.only('when a file is read successfully', function() {
beforeEach(function() {
sinon.stub(Q, 'nfcall').withArgs(fs.readFile, fileParams.path).returns(Q.resolve(file));
sinon.stub(Q, 'ninvoke').withArgs(S3, 'upload', params).returns(Q.resolve('url'));
chai.spy.on(Q, 'ninvoke')
});
it('Q.ninvoke should be called with args', function(done) {
UploadCommand.initialize(fileParams).then(function(data) {
expect(Q.ninvoke).to.have.been.called.with(S3, 'upload', params);
done();
});
});
});
You can use sinon to stub as shown below
let yourFunctionStub
yourFunctionStub= sinon.stub(yourClass, "yourFunction");
yourFunctionStub.withArgs(arg1, arg2, arg3).returns(resultYouWant);
if this a promise you can return
....returns(Promise.resolve(resultYouWant));
if for argument you are not clear you can you
sinon.match.any
Hope this helps you. :)
How would I go about stubbing S3 uploads in Node.js?
For insight, I'm using Mocha for tests and Sinon for stubbing, but I'm open to changing anything. I have a file that exports a function that performs the upload. It looks like this:
var AWS = require('aws-sdk');
var s3 = new AWS.S3({ params: { Bucket: process.env.S3_BUCKET }});
var params = { Key: key, Body: body };
s3.upload(params, function (error, data) {
// Handle upload or error
});
If I try to stub AWS.S3 or AWS.S3.prototype, nothing changes. I assume this is because my tests have required aws-sdk themselves and have their own copy of each function.
My test looks like this:
describe('POST /files', function () {
var url = baseURL + '/files';
it('uploads the file to s3', function (done) {
var fs = require('fs');
var formData = {
video: fs.createReadStream(process.cwd() + '/test/support/video.mp4')
};
var params = {url: url, formData: formData};
request.post(params, function (error, response, body) {
expect(response.statusCode).to.eq(200);
expect(response.body).to.eq('Uploaded');
done();
});
});
});
This test works fine, but it does not stub the upload to S3, so the upload actually goes through :X.
There are several options to mock S3 in Node.
Some modules specific to S3:
https://www.npmjs.com/package/mock-s3
https://www.npmjs.com/package/mock-aws-s3
Some modules for general AWS mocking:
https://www.npmjs.com/package/aws-sdk-mock
https://www.npmjs.com/package/mock-aws-sinon
https://www.npmjs.com/package/mock-aws
And you can even start a simple server that responds to some of the S3 API calls:
https://www.npmjs.com/package/s3rver
The last one can easily be used in other languages and runtimes, not only in Node.
You can stub with Sinon.js as follows if you'd like:
Expose the AWS.S3 instance:
var AWS = require('aws-sdk');
var s3 = new AWS.S3({ params: { Bucket: process.env.S3_BUCKET }});
var params = { Key: key, Body: body };
exports.s3.upload(params, function (error, data) {
});
//Expose S3 instance
exports.s3 = s3;
Stub the same instance like so:
var sinon = require('sinon');
//Import module you are going to test
var UploadService = require('./uploadService');
describe('POST /files', function () {
before(function() {
//Stub before, and specify what data you'd like in the callback.
this.uploadS3Stub = sinon.stub(uploadService.s3, 'upload').callsArgWith(1, null, { data: 'Desired response' });
});
after(function() {
//Restore after
this.uploadS3Stub.restore();
});
var url = baseURL + '/files';
it('uploads the file to s3', function (done) {
var fs = require('fs');
var formData = {
video: fs.createReadStream(process.cwd() + '/test/support/video.mp4')
};
var params = {url: url, formData: formData};
var self = this;
request.post(params, function (error, response, body) {
expect(response.statusCode).to.eq(200);
expect(response.body).to.eq('Uploaded');
//You can also check whether the stub was called :)
expect(self.uploadS3Stub.calledOnce).to.eql(true);
done();
});
});
});
s3UploadStub = AWS.S3.prototype.upload = sandbox.stub();
it("should upload successfully", async function() {
s3UploadStub.yields(null, data);
let response = await FileUtil.uploadFile(fileReference,data);
expect(s3UploadStub.calledOnce).to.be.true;
expect(response.statusCode).to.equal(200);
expect(response.body).to.equal(fileReference);
});
You need to create stubs using sinon and replace the variables in the code you're testing with the stubs. There are a few modules to do this, try Mockery or Rewire. For example with Rewire you load the module you're testing using rewire instead of require then use __set__ to set the variables.
var rewire = require('rewire');
var moduleUnderTest = rewire('myModule');
moduleUnderTest.__set__('s3', stubbedS3);