Open ifuyivara opened 7 years ago
@ifuyivara a Pull Request with more examples would be very welcome!
@ifuyivara I created a PR with an example on how to use the lib with Sinon spies: https://github.com/dwyl/aws-sdk-mock/pull/61
I haven't used aws-sdk-mock before either. But after I figured this out it really made testing a breeze.
Does anyone have an example of using aws-sdk-mock
with Jest? I'm trying but it doesn't seem to actually mock anything for me 😢
@hassankhan
/* global test, expect */
const AWS = require('aws-sdk-mock')
const AWS_SDK = require('aws-sdk')
const mod = require('../index')
const demoAttributeResponse = require('./fixtures/demo-get-instance-attribute.json')
test('retrieve and decode instance userdata', () => {
AWS.mock('EC2', 'describeInstanceAttribute', demoAttributeResponse)
const EC2 = new AWS_SDK.EC2({apiVersion: '2016-11-15'})
const InstanceId = 'i-1234567890abcdef0'
const userData = mod.getUserData(EC2, InstanceId)
expect(userData).toContain('ECS_CLUSTER')
AWS.restore()
})
I pass the service directly to the function instead of the function creating it itself. It's also important to create the service after the service method has been mocked:
AWS.mock('EC2', 'describeInstanceAttribute', demoAttributeResponse)
const EC2 = new AWS_SDK.EC2({apiVersion: '2016-11-15'})
Just echoing the need for real-world examples that test user-created code, not just ones that are empty or test that a mocked AWS service will return something when you call service "metadata functions" like describeInstanceAttribute.
I sense this is a very useful repo, I just can't figure out how to use it :-(
Can you elaborate on what kind of examples you need? Maybe create a PR with some user code and I will try to write the tests for it.
@hoegertn I'd like to take you up on your offer.
I spent a few hours trying to get this to work. i'm using jest. At one point i also tried adding mock-require
I have a class that I instantiate and provides access to AWS cognito functions.
For example
import AWS from 'aws-sdk'
class Cognito {
constructor(options) {
// .....bunch of init code
this.cognitoService = new AWS.CognitoIdentityServiceProvider({
region: this.region,
accessKeyId: this.accessKeyId,
secretAccessKey: this.secretAccessKey
})
this.srp = new SRPClient(this.userPoolId.split('_')[1])
AWS.config.region = this.region
}
async create(email, password = null, attributes = {}) {
await this._adminCreateUser(email, password, attributes)
return this.adminGetUser(email)
}
async _adminCreateUser(username, password = null, attributes = {}, messageAction = null) {
return new Promise((resolve, reject) => {
// .....
// ..........bunch of stuff
// ....
this.cognitoService.adminCreateUser(params, (err, data) => {
if (err) {
reject(new CognitoError('create', err))
} else {
resolve(data)
}
})
})
}
async adminGetUser(username) {
return new Promise((resolve, reject) => {
// .....
// ..........bunch of stuff
// ....
this.cognitoService.adminGetUser(params, async (userErr, user) => {
if (userErr) {
reject(userErr)
} else {
const { Groups } = await this.getGroupsForUser(user.Username)
user.groups = Groups
resolve(user)
}
})
})
}
}
so in this example I have
I basically couldn't even get the actual mocking of the AWS module to happen.
If there is an example of how to do this somewhere, i'd appreciate it if you could point me in the correct direction. Thanks
I will have a look at it. Is there a special reason you do not use the promise() method of the AWS client objects instead of creating your own Promises?
awsClient.method(params).promise()
I will have a look at it. Is there a special reason you do not use the promise() method of the AWS client objects instead of creating your own Promises?
No. I didn't write the library and am not very familiar with it.
I'll take a look at how we might use the AWS client object promises.
After I posted, I kept researching and found an obscure forum post somewhere with someone posting some sample code they ddi for DynamoDB.
They have a similar setup except that for their library, instead of instantiating a AWS client object in the constructor, they pass one in as a parameter.
I may attempt refactoring the existing code to do this instead of how i've put it in the example but I have to get buy in as the module is already being used elsewhere..
I'm also trying to figure out how to mock receiving an SQS Message from SNS Topic subscription then mock downloading a file from S3 with the file in the message.
Alright so I got this library working, I was just a bit confused on how it works. It turned out my issue was from ES6/webpack. The library needs to know the location of the AWS lib or you need to give it an instance of AWS to apply the mocks to.
The documentation could benefit from a hello world block right at the top so you understand how you're meant to use it. It's got stacks of useful info in there but the first time you try to use the lib, it's a bit confusing. It could also be that JS isn't my primary language.
Test code:
import AWS from 'aws-sdk-mock';
import * as dynamoDbLib from "../src/libs/dynamodbLib";
test('foo', async () => {
// This is the key line I was missing
AWS.setSDKInstance(dynamoDbLib.getAWS());
AWS.mock('DynamoDB.DocumentClient', 'get', function (params, callback) {
callback(null, { Item: { Key: 'Value' } });
});
const params = {
TableName: 'stop-mocking-me',
Key: {
id: 1234,
}
};
let result = await dynamoDbLib.call("get", params);
expect(result.Item).toEqual({ Key: 'Value' });
AWS.restore('DynamoDB.DocumentClient');
});
My lib file from the serverless tutorial with an extra function:
import AWS from "aws-sdk";
AWS.config.update({ region: "ap-southeast-2" });
export function call(action, params) {
const dynamoDb = new AWS.DynamoDB.DocumentClient();
return dynamoDb[action](params).promise();
}
export function getAWS() {
return AWS;
}
If you are using jest, you can create your own fully mocked instances of the AWS SDK without AWS-sdk-mock. I mix sinon spies/stubs into jest as they are a lot more functional than Jest mocks however this will work with jest mocks as well. The code below also shows how to return synchronous promises that you can then invoke after running test code to control how the mock AWS SDK responses.
import * as sinon from 'sinon';
import * as AWS from 'aws-sdk';
import { SynchronousPromise } from 'synchronous-promise';
const mockDocumentClient = {
batchWrite: sinon.stub().callsFake(() => {
return {
promise: sinon.spy(SynchronousPromise.unresolved)
};
}),
update: sinon.stub().callsFake(() => {
return {
promise: sinon.spy(SynchronousPromise.unresolved)
};
})
};
jest.mock('aws-sdk', () => {
return {
DynamoDB: {
DocumentClient: () => {
return mockDocumentClient;
}
}
}
});
describe('Module that uses AWS SDK', () => {
it('example', () => {
moduleUnderTest.test();
mockDocumentClient.update.returnValues[0]
.promise.returnValues[0].resolve({FakeDynamoDBResponse: 'Make Me Real'});
});
}
holy crap. can't believe I got this working! I'm using classes and thought this would be impossible due to constraints in documentation. I got it working with "rewire", "unit.js" and "aws-sdk-mock"
# In the Test
'use strict';
const AWS = require('aws-sdk');
const AWSMock = require('aws-sdk-mock');
const test = require('unit.js');
const rewire = require('rewire');
// const path = require('path');
const sampleEvent = {
// ... sensitive data
};
const sampleContext = {
// ... sensitive data
};
process.env.S3_BUCKET = 'yourbucket.domain.com';
process.env.SNS_TOPIC_ERROR = 'error::Arn';
process.env.SNS_TOPIC_SUCCESS = 'Succees::Arn';
// const realAWS = etlFinishedNormal.getAws();
AWSMock.setSDKInstance(AWS);
// AWSMock.setSDK(path.resolve('node_modules/aws-sdk'));
describe('Tests the ETLFinished lambda function', function() {
it('verifies moveFilesFromEtlToRaw references our dummy file', async () => {
const dummyResponse = ['cheeseburger'];
try {
AWSMock.mock('S3', 'getObject', function (params, callback){
console.log('------------- IT WORKS ------------');
console.log(params);
// console.log(process.env.S3_BUCKET);
test.assert(params.Bucket === process.env.S3_BUCKET);
callback(null, dummyResponse);
});
// const etlFinishedNormal = require('../../lambda/etlFinished/index.js');
const etlFinishedIndex = rewire('../../lambda/etlFinished/index.js');
const ETLFinished = etlFinishedIndex.__get__('ETLFinished');
const etlFinished = new ETLFinished(sampleEvent, sampleContext);
let result = await etlFinished.moveFilesFromEtlToRaw();
test.assert(result === dummyResponse);
} catch(error) {
throw error;
// done(error);
}
});
});
# The lambda function
const AWS = require('aws-sdk');
const sns = new AWS.SNS();
const s3 = new AWS.S3();
class ETLFinished {
constructor(lambdaEvent, lambdaContext) {
this.snsTopicSuccess = process.env.SNS_TOPIC_SUCCESS;
this.snsTopicError = process.env.SNS_TOPIC_ERROR;
this.bucket = process.env.S3_BUCKET;
this.lambdaEvent = lambdaEvent;
this.lambdaContext = lambdaContext;
// Example JobName: 'fis-monetary'.
[this.processor, this.jobName] = lambdaEvent.detail.jobName.split('-');
// this.moveFilesFromEtlToRaw.bind(this)
}
async moveFilesFromEtlToRaw() {
const s3ListObjectsParams = {
Bucket: this.bucket,
// Prefix: ['etl', this.processor, this.jobName].join('/'),
Key: 'etl/foo/bar/whatever.txt'
};
console.log('s3ListObjectsParams: ', s3ListObjectsParams);
return new Promise((resolve, reject) => {
s3.getObject(s3ListObjectsParams, (err, data) => {
if (err) reject(console.log('ERR: ', err, err.stack)); // an error occurred
console.log('data: ', data);
resolve(data);
});
});
}
}
Could someone provide a simple example for testing response from IAM.listUsers()?
Example with Jest `'use strict';
// Generated by serverless-jest-plugin import uuid from 'uuid/v1';
const AWS = require('aws-sdk'); const AWSMock = require('aws-sdk-mock');
AWSMock.setSDKInstance(AWS);
const message = { Messages: [ { Body: JSON.stringify({ category: 'business', requestType: 'create' }) } ] };
AWSMock.mock('SQS', 'receiveMessage', (params, callback) => { callback(null, message); });
const mod = require('./../controllers/delivery/transactions-sns'); const jestPlugin = require('serverless-jest-plugin');
const { lambdaWrapper } = jestPlugin; const wrapped = lambdaWrapper.wrap(mod, { handler: 'handler' });
describe('TransactionsSns', () => { beforeAll((done) => { done(); });
it('test that ok response structure is correct', async () => { return wrapped.run().then((response) => { console.log('success response: ', response); expect(response).toMatchObject({ statusCode: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': true }, body: JSON.stringify({}) }); }); }, 50000);
afterAll(() => { AWSMock.restore('SQS', 'receiveMessage'); }); }); `
A trick to make aws-sdk-mock
compatible with real-world example is to pass the SDK location to AWSMock.setSDK
before loading anything. Example below is written in typescript but you can easily convert it to a javascript equivalent:
import AWSMock from 'aws-sdk-mock';
// load aws-sdk in ../model/dynamodb
import { DataStore } from '../model/dynamodb';
// make aws-sdk mockable everywhere
AWSMock.setSDK(resolve(__dirname, '..', 'node_modules', 'aws-sdk'));
describe('model:dynamodb', () => {
// accept any write to DynamoDB
AWSMock.mock('DynamoDB.DocumentClient', 'put', (_: any, callback: Function) =>
callback()
);
test('createItinerary', async () => {
const store = new DataStore();
/* tests below */
});
});
@alvis thank you for this, from the documentation it's not immediately clear that you need to do this and it finally solved my issue.
🎉 It would be amazing if someone would like to submit a PR to the documentation to make things clearer 😊
I struggled myself with the client initialising because my service was initialised outside the function being tested and reused by multiple different functions that the module exported.
From README: "NB: The AWS Service needs to be initialised inside the function being tested in order for the SDK method to be mocked"
So to be able to mock and unit test you need to do it this way but is there a big performance difference?
@alvis I bet your DataStore() function includes "new AWS.DynamoDB.DocumentClient();" right?
@teemuniiranen correct! My DataStore is a just class which has a private new AWS.DynamoDB.DocumentClient()
Thank you @teemuniiranen So for me, I had to do 2 things;
In my test file add:
AWS.setSDKInstance(require('aws-sdk'));
And then, in my API, move the initialization of new AWS.S3(...)
to be within the method being tested. I am not sure on the performance implications of this (as mentioned), but it works!
A trick to make
aws-sdk-mock
compatible with real-world example is to pass the SDK location toAWSMock.setSDK
before loading anything. Example below is written in typescript but you can easily convert it to a javascript equivalent:import AWSMock from 'aws-sdk-mock'; // load aws-sdk in ../model/dynamodb import { DataStore } from '../model/dynamodb'; // make aws-sdk mockable everywhere AWSMock.setSDK(resolve(__dirname, '..', 'node_modules', 'aws-sdk')); describe('model:dynamodb', () => { // accept any write to DynamoDB AWSMock.mock('DynamoDB.DocumentClient', 'put', (_: any, callback: Function) => callback() ); test('createItinerary', async () => { const store = new DataStore(); /* tests below */ }); });
So I tried this and didn't work. I'm using a class to initiate the service. After a lot of struggling to make it work, I found that you can't import the service directly: you must import AWS
:
// Doesn't work
import SES from 'aws-sdk/clients/ses';
//This works
import AWS from 'aws-sdk';
const { SES } = AWS;
Full example (Typescript):
// File-to-test.ts
import AWS from 'aws-sdk';
import { Template } from 'aws-sdk/clients/ses';
export default class SESService {
private client: AWS.SES;
constructor() {
this.client = new AWS.SES({
region: 'us-east-1',
});
}
async createTemplate(data: Template): Promise<boolean> {
try {
const response = await this.client
.createTemplate({ Template: data })
.promise();
console.log(response);
return true;
} catch (error) {
console.log(error);
return false;
}
}
}
// File-to-test.spec.ts
import AWS from 'aws-sdk-mock';
import SESService from '@services/File-to-test.ts';
import { Template } from 'aws-sdk/clients/ses';
AWS.setSDK(resolve(__dirname, '..', '..', '..', 'node_modules', 'aws-sdk'));
AWS.mock('SES', 'createTemplate', () => Promise.resolve(true));
describe('SES Service', () => {
it('should be able to create a new template in AWS', async () => {
const importHTMLService = new SESService();
const TemplateData: Template = {
TemplateName: 'TemplateTest',
HtmlPart: '<html><body><h1>Hello World!</h1></body></html>',
SubjectPart: 'Test subject',
};
const response = await SESService.createTemplate(TemplateData);
expect(response).toBe(true);
});
});
test('foo', async () => { // This is the key line I was missing AWS.setSDKInstance(dynamoDbLib.getAWS()); AWS.mock('DynamoDB.DocumentClient', 'get', function (params, callback) { callback(null, { Item: { Key: 'Value' } }); }); const params = { TableName: 'stop-mocking-me', Key: { id: 1234, } }; let result = await dynamoDbLib.call("get", params); expect(result.Item).toEqual({ Key: 'Value' }); AWS.restore('DynamoDB.DocumentClient'); });
How would you adapt this to work within an express route? I've tried awaiting a response using chai but I keep getting various errors and timeouts.
I've replicated the example and it does seem to work.
This is a non-working example that calls a route:
import * as dynamoDbLib from "dynamoDbLib";
router.get('/', (req, res) => {
const params = {
TableName: 'some_table',
};
dynamoDbLib.call('scan', params, function (err, data) {
if (err) {
res.status(500).send({
success: false,
message: 'Error: Server error',
});
} else {
const { Items } = data;
res.send({
success: true,
message: 'Loaded users',
users: Items,
});
}
});
});
the test I have is:
import * as dynamoDbLib from "dynamoDbLib";
import AWSMock from 'aws-mock-sdk';
it('should GET /', async () => {
AWSMock.setSDKInstance(dynamoDbLib.getAws());
AWSMock.mock('DynamoDB.DocumentClient', 'scan', function (params, callback) {
callback(null, { Items: { Key: 'Value' } });
});
const res = await chai.request(app).get('/');
expect(res.body).not.to.be.empty;
AWSMock.restore('DynamoDB');
});
and the error:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I've also tried making the route async and adding the done argument to the test, neither works.
Just a quick note that was easy for me to glance over and was critical for TypeScript:
// Does not work, which is what the documentation states:
import * as AWS from 'aws-sdk';
// Changing the line above to this got it working for me:
import AWS from 'aws-sdk';
...
// Later in my code...
AWSMock.setSDKInstance(AWS);
AWSMock.mock('SQS', 'receiveMessage', () => Promise.resolve(mockS3Event));
After fixing this my mocks started working.
@aesimano thank you for your note! Saved me several hours of banging my head against the wall.
I'm really trying to use this module to test our app. But I can't wrap my head around how to actually use it in a real scenario where I need to test some of the services our app is using. Here is what I'm doing:
Resolver.start is a function inside the resolver service that starts the AWS sdk like so:
Nothing gets mocked and my receiveMessage doesn't get called.
I tried several things but just can't seem to get this to work. Am I ordering the includes wrong?