Trying to write a unittest for the below module in /utility/sqsThing.js. However I'm having diffuculty mocking the sqs.sendMessage method. Anyone know how I should go about this. I'm using the sinon library, and mocha for running the tests.
The function that I'm trying to unittest utility/sqsThing.js:
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
My attempt at mocking the sqs.sendMessage method in a unittest sqsThingTest.js:
const sqsOutputResultSender = require('../utility/sqsThing');
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
Running this unittest with mocha tests/unit/sqsThingTest.js however I get:
AssertionError: expected undefined to deeply equal 'test'.
info: Error AccessDenied: Access to the resource https://sqs.us-east-1.amazonaws.com/ is denied..
It looks like the mock did not replace the aws api call. Anyone know how I can mock sqs.SendMessage in my test?
You could use rewire js it is a library that lets you inject mocked properties into your module you want to test.
Your require statement would look something like this:
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
Rewire will allow you to mock everything in the top-level scope of you sqsThing.js file.
Also you need to return the value of sqs.sendMessage this will remove the issue expected undefined to deeply equal 'test'
Your original file would look the same just with a return statement.
//utility/sqsThing.js
const AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const outputQueURL = 'https:awsUrl';
const SQSOutputSender = (results) => {
const params = {
MessageBody: JSON.stringify(results),
QueueUrl: outputQueURL,
};
// Method that I want to mock
return sqs.sendMessage(params, function (err, data) {
if (err) {
console.log('Error');
} else {
console.log('Success', data.MessageId);
}
});
};
You would then write your unit test as follows:
//sqsThingTest.js
var rewire = require("rewire");
var sqsOutputResultSender = rewire('../utility/sqsThing');
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sqsOutputResultSender.__set__("sqs", {
sendMessage: function() { return 'test' }
});
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
This example returns an object with a property of sendMessage but this could be replaces with a spy.
Rewire Docs
Try moving the declaration of sqsOutputResultSender after you have stubbed the sendmessage function
var sqsOutputResultSender;
const AWS = require('aws-sdk');
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const mochaccino = require('mochaccino');
const { expect } = mochaccino;
const sinon = require('sinon');
describe('SQS thing test', function() {
beforeEach(function () {
sinon.stub(sqs, 'sendMessage').callsFake( function() { return 'test' });
sqsOutputResultSender = require('../utility/sqsThing');
});
afterEach(function () {
sqs.sendMessage.restore();
});
it('sqsOutputResultSender.SQSOutputSender', function() {
// Where the mock substitution should occur
const a = sqsOutputResultSender.SQSOutputSender('a');
expect(a).toEqual('test');
})
});
Related
**Edit: Re-written with a simple example that works first:
So I've got a test file and 2 modules.
moduleA has a dependency, moduleB
// moduleA.js
const ModuleB = require('./moduleB');
function functionA() {
return 20 + ModuleB.functionB();
};
module.exports = { functionA };
// moduleB.js
const functionB = () => {
return 10;
}
module.exports = { functionB }
My test file stubs out functionB (returned from moduleB) using proxyquire:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('Unit Tests', function() {
it('should work', () => {
const mockedFn = sinon.stub();
mockedFn.returns(30);
const copyModuleA = proxyquire('./moduleA', {
'./moduleB': {
functionB: mockedFn
}
});
console.log(copyModuleA.functionA());
})
});
So it outputs 50 (stubbed functionB 30 + functionA 20)
Now I'm trying to take this example into my code:
moduleA in this case is a file called validation.js. It is dependent on moduleB, in this case a sequelize model, Person, with the function I want to mock: findOne
validation.js exports module.exports = { validateLogin };, a function that calls validate, which returns a function that uses Person.findOne()
So in my mind, as with the simple example, I need to create a stub, point to the validation module in proxyquire, and reference the dependency and its findOne function. Like this:
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
This should stub Person.findOne in validation.js. But it doesn't seem to. And I have no idea why.
let validationModule;
describe('Unit Tests', () => {
before(() => {
const stubbedFindOne = sinon.stub();
stubbedFindOne.resolves();
validationModule = proxyquire('../../utils/validation', {
'../models/Person': {
findOne: stubbedFindOne
}
});
})
it.only('should return 422 if custom email validation fails', async() => {
const wrongEmailReq = { body: {email: 'nik#hotmail.com'} };
const res = {
statusCode: 500,
status: (code) => {this.statusCode = code; return this},
};
const validationFn = validationModule.validateLogin();
const wrongEmail = await validationFn(wrongEmailReq, res, ()=>{});
expect(wrongEmail.errors[0].msg).to.be.equal('Custom Authorisation Error');
return;
})
And this is my validation.js file:
const Person = require('../models/Person');
// parallel processing
const validate = validations => {
return async (req, res, next) => {
await Promise.all(validations.map(validation => validation.run(req)));
const errors = validationResult(req);
if (errors.isEmpty()) {
return next();
}
const error = new Error();
error.message = process.env.NODE_ENV === 'development'? 'Validation Failed':'Error';
error.statusCode = !errors.isEmpty()? 422:500;
error.errors = errors.array({onlyFirstError: true});
next(error);
return error;
};
};
const validateLogin = () => {
const validations = [
body('email')
.isString()
// snip
.custom(async (value, {req}) => {
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
})
.trim(),
];
return validate(validations);
}
module.exports = {
validateLogin
};
So the code in both the small sample and my app is correct, apart from how I stub the function. It shouldn't resolve or reject anything (I tried both out of desperation). It should return null in order to satisfy the conditional rather than jump to the catch block:
try{
const person = await Person.findOne({ where: { email: value } });
if(!person) return Promise.reject('Custom Authorisation Error');
} catch(err) {
throw err;
}
Hope the simple example helps someone else with proxyquire though
I'm having trouble getting the AWS Secrets Manager module mocked for the jest unit tests... The part it errors on is the .promise(). When I remove that, the code doesn't work for the real Secrets Manager so I think it needs to stay there. How do I mock the getSecretData function so that getSecretData.promise() will work for the mock?
Here is the SecretsManager.js code:
import AWS from 'aws-sdk';
export class SecretsManager {
constructor() {
AWS.config.update({
region: 'us-east-1',
});
this.secretsManager = new AWS.SecretsManager();
}
async getSecretData(secretName) {
try {
const response = await this.secretsManager.getSecretValue({
SecretId: secretName,
}).promise();
const secretString = response.SecretString;
const parsedSecret = JSON.parse(secretString);
return parsedSecret;
} catch (e) {
console.log('Failed to get data from AWS Secrets Manager.');
console.log(e);
throw new Error('Unable to retrieve data.');
}
}
}
Here is the SecretsManager.test.js code:
import { SecretsManager } from '../utils/SecretsManager';
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
async getSecretValue({
SecretId: secretName
}) {
return {
promise: function () {
return {
UserName: 'test',
Password: 'password',
};
}
};
}
};
}
}
});
describe('SecretsManager.js', () => {
describe('Given I have a valid secret name', () => {
describe('When I send a request for test_creds', () => {
it('Then the correct data is returned.', async () => {
const mockReturnValue = {
UserName: 'test',
Password: 'password',
};
const logger = getLogger();
const secretManager = new SecretsManager();
const result = await secretManager.getSecretData('test_creds');
expect(result).toEqual(mockReturnValue)
});
});
describe('When I send a request without data', () => {
it('Then an error is thrown.', async () => {
const secretManager = new SecretsManager();
await expect(secretManager.getSecretData()).rejects.toThrow();
});
});
});
});
This is the error I get when running the tests:
this.secretsManager.getSecretValue(...).promise is not a function
Any suggestions or pointers are greatly appreciated!
Thank you for looking at my post.
I finally got it to work... figures it'd happen shortly after posting the question, but instead of deleting the post I'll share how I changed the mock to make it work incase it helps anyone else.
Note: This is just the updated mock, the tests are the same as in the question above.
// I added this because it's closer to how AWS returns data for real.
const mockSecretData = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
}
jest.mock('aws-sdk', () => {
return {
config: {
update(val) {
},
},
SecretsManager: function () {
return {
getSecretValue: function ( { SecretId } ) {
{
// Adding function above to getSecretValue: is what made the original ".promise() is not a function" error go away.
if (SecretId === 'test_creds') {
return {
promise: function () {
return mockSecretData;
}
};
} else {
throw new Error('mock error');
}
}
}
};
}
}});
I ran into this issue as well. There may be a more elegant way to handle this that also allows for greater control and assertion, but I haven't found one. Note that the in-test option may work better with newer versions of Jest.
I personally solved this issue by making use of manual mocks and a custom mock file for aws-sdk. In your case, it would look something like the following:
# app_root/__tests__/__mocks__/aws-sdk.js
const exampleResponse = {
ARN: 'x',
Name: 'test_creds',
VersionId: 'x',
SecretString: '{"UserName":"test","Password":"password"}',
VersionStages: ['x'],
CreatedDate: 'x'
};
const mockPromise = jest.fn().mockResolvedValue(exampleResponse);
const getSecretValue = jest.fn().mockReturnValue({ promise: mockPromise });
function SecretsManager() { this.getSecretValue = getSecretValue };
const AWS = { SecretsManager };
module.exports = AWS;
Then in your test file:
// ... imports
jest.mock('aws-sdk');
// ... your tests
So, in a nutshell:
Instead of mocking directly in your test file, you're handing mocking control to a mock file, which Jest knows to look for in the __mocks__ directory.
You create a mock constructor for the SecretsManager in the mock file
SecretsManager returns an instance with the mock function getSecretValue
getSecretValue returns a mock promise
the mock promise returns the exampleResponse
Bada boom, bada bing. You can read more here.
I ran into a same issue, I have tried to solve as below. It worked perfectly in my case.
Terminalsecret.ts
import AWS from 'aws-sdk';
AWS.config.update({
region: "us-east-1",
});
const client = new AWS.SecretsManager();
export class Secret {
constructor(){}
async getSecret(secretName: string) {
let secret: any;
const data = await client.getSecretValue({ SecretId: secretName).promise();
if ('SecretString' in data) {
secret = data.SecretString;
} else {
const buff = Buffer.alloc(data.SecretBinary as any, 'base64');
secret = buff.toString('ascii');
}
const secretParse = JSON.parse(secret);
return secretParse[secretName];
}
}
Terminalsecret.test.ts
import { SecretsManager as fakeSecretsManager } from 'aws-sdk';
import { Secret } from './terminalSecret';
jest.mock('aws-sdk');
const setup = () => {
const mockGetSecretValue = jest.fn();
fakeSecretsManager.prototype.getSecretValue = mockGetSecretValue;
return { mockGetSecretValue };
};
describe('success', () => {
it('should call getSecretValue with the argument', async () => {
const { mockGetSecretValue } = setup();
mockGetSecretValue.mockReturnValueOnce({
promise: async () => ({ SecretString: '{"userName": "go-me"}' })
});
const fakeName = 'userName';
const terminalSecretMock: TerminalSecret = new TerminalSecret()
terminalSecretMock.getTerminalSecret(fakeName);
expect(mockGetSecretValue).toHaveBeenCalledTimes(1);
});
});
In my calendar.spec.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
...
before(() => {
sinon.stub(googleCalendar.calendarList, 'list').resolves({ data: true })
})
after(() => {
googleCalendar.calendarList.list.restore()
})
In my calendar.js, I have:
const { google } = require('googleapis')
const googleCalendar = google.calendar('v3')
let { data } = await googleCalendar.calendarList.list({
auth: oauth2Client
})
But it doesn't appear to be stubbed. It goes ahead and tries to connect to Google Calendar. What am I doing wrong?
You can mock the entire googleapis module with mock-require.
const mock = require('mock-require');
mock('googleapis', {
google: {
calendar: () => ({
calendarList: {
list: () => {
return Promise.resolve({
data: {
foo: 'bar'
}
});
}
}
})
}
});
Once you mocked it, your module will consume the mocked module instead of the original so you can test it. So if you module is exposing a method that calls the API, something like that:
exports.init = async () => {
const { google } = require('googleapis');
const googleCalendar = google.calendar('v3');
let { data } = await googleCalendar.calendarList.list({
auth: 'auth'
});
return data;
}
The test will be
describe('test', () => {
it('should call the api and console the output', async () => {
const result = await init();
assert.isTrue(result.foo === 'bar');
});
});
Here is a small repo to play with it: https://github.com/moshfeu/mock-google-apis
I have the following Firebase Function that makes use of Auth0 to get a user profile.
'use strict';
const {
dialogflow,
Image,
} = require('actions-on-google')
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
// database collection and key names
const DB_BANK_COLLECTION_KEY = 'bank'
// the action name from all Dialogflow intents
const INTENT_WELCOME_USER = 'Default Welcome Intent';
// Initialize the Auth0 client
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: functions.config().familybank.auth0.domain,
clientID: functions.config().familybank.auth0.clientid
});
const app = dialogflow();
app.intent(INTENT_WELCOME_USER, async (conv) => {
console.log('Request: ' + JSON.stringify(conv.request));
const userInfo = await auth0.getProfile(conv.user.access.token)
.catch( function(err) {
console.error('Error getting userProfile from Auth0: ' + err);
conv.close("Something went wrong. Please try again in a few minutes. " + err)
});
console.log('userInfo: ' + JSON.stringify(userInfo));
// check for existing bank, if not present, create it
var bankRef = db.collection(DB_BANK_COLLECTION_KEY).doc(userInfo.email);
const bankSnapshot = await bankRef.get()
})
exports.accessAccount = functions.https.onRequest(app);
I tried to mock auth0 in my tests using the following code (and several permutations), but the actual function always gets called instead of the mock.
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
// Require firebase-admin so we can stub out some of its methods.
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();
var AuthenticationClient = require('auth0').AuthenticationClient;
var auth0 = new AuthenticationClient({
domain: "mock",
clientID: "mock"
});
describe('Cloud Functions', () => {
let myFunctions, adminInitStub;
before(() => {
test.mockConfig({"familybank": {"auth0": {"domain": "mockdomain", "clientid": "mockid"}}});
adminInitStub = sinon.stub(admin, 'initializeApp');
sinon.stub(admin, 'firestore')
.get(function() {
return function() {
return "data";
}
});
sinon.stub(auth0, 'getProfile').callsFake( function fakeGetProfile(accessToken) {
return Promise.resolve({"email": "daniel.watrous#gmail.com", "accessToken": accessToken});
});
myFunctions = require('../index');
});
after(() => {
adminInitStub.restore();
test.cleanup();
});
describe('accessAccount', () => {
it('should return a 200', (done) => {
const req = {REQUESTDATA};
const res = {
redirect: (code, url) => {
assert.equal(code, 200);
done();
}
};
myFunctions.accessAccount(req, res);
});
});
})
Is there some way to mock auth0 for my offline tests?
I discovered that rather than initialize the Auth0 AuthenticationClient, I could first require the UsersManager, where the getProfile (which wraps getInfo) is defined.
var UsersManager = require('auth0/src/auth/UsersManager');
In my before() method, I can then create a stub for getInfo, like this
sinon.stub(UsersManager.prototype, 'getInfo').callsFake( function fakeGetProfile() {
return Promise.resolve({"email": "some.user#company.com"});
});
All the calls to auth0.getProfile then return a Promise that resolves to the document shown in my stub fake function.
How can I stub the redis publish method?
// module ipc
const redis = require('redis');
module.exports = class IPC {
constructor() {
this.pub = redis.createClient();
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
and another module
// module service
module.exports = class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
How could I stub the private variable pub in IPC class?
I could stub the redis.createClient by using proxyquire, if I do that it will complain publish undefined
My current test code
let ipcStub;
before(() => {
ipcStub = proxyquire('../ipc', {
redis: {
createClient: sinon.stub(redis, 'createClient'),
}
})
});
it('should return true', () => {
const ipc = new ipcStub();
const ipcPublishSpy = sinon.spy(ipc, 'publish')
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipcPublishSpy.calledOnce);
})
You just need to set the spy on the publish method, no need for the proxyquire.
e.g.
import {expect} from 'chai';
import sinon from 'sinon';
class IPC {
constructor() {
this.pub = {
publish:() => {} //here your redis requirement
};
}
publish(data) {
this.pub.publish('hello', JSON.stringify(data));
}
}
class Service {
constructor(ipc) {
this.ipc = ipc;
}
sendData() {
this.ipc.publish({ data: 'hello' })
}
}
describe('Test Service', () => {
it('should call publish ', () => {
const ipc = new IPC;
sinon.spy(ipc.pub,'publish');
const service = new Service(ipc);
service.sendData();
expect(ipc.pub.publish.calledOnce).to.be.true;
});
});
I found a way to do it just by using sinon
Just need to create a stub instance using sinon.createStubInstance,
then this stub will have all the functionalities from sinon without the implementation of the object (only the class method name)
let ipcStub;
before(() => {
ipcStub = sinon.createStubInstance(IPC)
});
it('should return true', () => {
const ipc = new ipcStub();
const service = new Service(ipc);
service.sendData();
assert.strictEqual(true, ipc.publishSystem.calledOnce);
})