Mocking Node.js module in Sinon still calls actual module - javascript

I'm starting to write tests for a node.js server I'm writing. I'm trying to test this function
exports.get_device_states = function (body, master_callback) {
var state = new State(body[State.id_key]);
console.log("STATE: ");
console.log(state);
mysqlDao.get_device_states_for_group(state, function(err, result) {
var resp_json = {
'error': err,
'stateList': result
}
master_callback(resp_json);
});
}
I need to mock the 'mysqlDao' module that I use to read/write to my db. but I was unable to do this. Whenever I try. I still get the actual db results back.
messagesTest.js
var sandbox = sinon.sandbox.create();
var stubbedDao = {
get_device_states_for_group: sandbox.stub().returns("STUBBED")
}
...
describe('messages', function() {
before(function() {
mockery.enable();
});
beforeEach(function() {
mockery.registerAllowable('async');
mockery.registerAllowable('../models/State');
mockery.registerAllowable('../models/Message');
mockery.registerMock('../dao/gcmdao', stubbedDao);
messages = require('../business/messages.js');
});
afterEach(function() {
sandbox.verifyAndRestore();
mockery.deregisterAll();
});
after(function() {
mockery.disable();
});
describe('get_device_state', function () {
it('isNormal', function () {
var body = {
"STATE_ID": testData.state_one
}
stubbedDao.get_device_states_for_group.yields();
messages.get_device_states(body, function(resp) {
console.log("returns");
assert(stubbedDao.get_device_states_for_group.calledOnce);
console.log(resp);
})
});
});
});
But instead of returning "STUBBED" Like I want it to. It just returns the Db results. It's not a huge issue for read functions but I need this to work so that I don't modify the db on other tests.
Thanks,
A.L

Related

Mocking Secrets Manager module for JavaScript jest unit tests

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);
});
});

mongodb mapreduce finalize function isn't working from javascript (same code is working from shell)

I have the following strange situation. I've started writing map/reduce jobs for MongoDB. I now have code (see Map/Reduce/Finalize functions in code below) that is working, except for the "finalize" step. The same code, ran through the mongo shell is producing the expected results (file_count field is added to the result, based upon arraylength. Both JS and Shell command populate the files_per_disc collection equally (files array, per disc, contains the same results). I already tried setting jsMode to true/false.
Any help / Suggestions?
Thanks!!
Sander
var mongojs = require('mongojs');
var db = mongojs('mongodb://super.local/sha', ['files', 'files_per_disc']);
db.on('error', function(err) {
console.log('database error', err)
})
db.on('connect', function() {
console.log('database connected')
})
var files_per_disc = {};
files_per_disc.execute = function() {
console.log('execute');
var mapper = function() {
var key = this.disc; // Disc
var mapObject = {
files: [this.filename]
};
if (this.filesize > 5000000000) {
emit(key, mapObject);
}
};
var reducer = function(key, values) {
var reducedObject = {
files: []
};
values.forEach(function(value) {
value.files.forEach(function(item) {
if (reducedObject.files.indexOf(item) == -1) {
reducedObject.files.push(item);
}
});
});
return reducedObject;
};
var finalizer = function(key, reducedObject) {
if (reducedObject.files) {
reducedObject.file_count = reducedObject.files.length;
}
return reducedObject;
};
db.files.mapReduce(
mapper,
reducer, {
jsMode: false,
out: 'files_per_disc',
finalize: finalizer
},
function(err, result) {
if (err) {
return console.log(err)
};
return console.log(result);
}
);
};
module.exports = files_per_disc;

node js unit testing: mocking require dependency

I am having issues writing unit test for the following setup as a jira.js file (in a node.js module):
var rest = require('restler'); // https://www.npmjs.com/package/restler
module.exports = function (conf) {
var exported = {};
exported.getIssue = function (issueId, done) {
...
rest.get(uri).on('complete', function(data, response) {
...
};
return exported;
};
Now, i want to write unit test for my getIssue function. 'restler' is a REST client through which i make REST calls to the JIRA API to get a JIRA issue via my code.
So to be able to test createIssue(..), I want to be able to mock the 'rest' var in my Jasmine unit tests.
How can i mock this method? Please give me some pointers so that i can go ahead. I have tried using rewire but i have failed.
This is what i have so far which does not work (ie. getIssue method turns out to be undefined):
var rewire = require("rewire");
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter();
var cfg = require("../../../config.js").Configuration;
var jiraModule = rewire("../lib/jira")(cfg);
var sinon = require("sinon");
var should = require("should");
// https://github.com/danwrong/restler
var restMock = {
init : function () {
console.log('mock initiated'+JSON.stringify(this));
},
postJson : function (url, data, options) {
console.log('[restler] POST url='+url+', data= '+JSON.stringify(data)+
'options='+JSON.stringify(options));
emitter.once('name_of_event',function(data){
console.log('EVent received!'+data);
});
emitter.emit('name_of_event', "test");
emitter.emit('name_of_event');
emitter.emit('name_of_event');
},
get : function (url, options) {
console.log('[restler] GET url='+url+'options='+JSON.stringify(options));
},
del : function (url, options) {
console.log('[restler] DELETE url='+url+'options='+JSON.stringify(options));
},
putJson : function (url, data, options) {
console.log('[restler] PUT url='+url+', data= '+JSON.stringify(data)+
'options='+JSON.stringify(options));
}
};
var cfgMock = {
"test" : "testing"
};
jiraModule.__set__("rest", restMock);
jiraModule.__set__("cfg", cfgMock);
console.log('mod='+JSON.stringify(jiraModule.__get__("rest")));
describe("A suite", function() {
it("contains spec with an expectation", function() {
restMock.init();
restMock.postJson(null, null, null);
console.log(cfg.jira);
// the following method turns out to be undefined but when i console.log out the jiraModule, i see the entire code outputted from that file
jiraModule.getIssue("SRMAPP-130", function (err, result) {
console.log('data= '+JSON.stringify(result));
});
expect(true).toBe(true);
});
});
If someone can guide me on how to mock the 'rest' require dependency & unit test this method that will be very helpful.
Also, how should i mock the 'conf' being passed to module.exports?
thanks
You could use proxyquire or mockery to stub/mock the dependencies.
In the below example I have used proxyquire. Hope it helps.
/* ./src/index.js */
var rest = require('restler');
module.exports = function (conf) {
var exported = {};
exported.getIssue = function (issueId, done) {
var uri = '';
var reqObj = '';
var service = {
auth : ''
};
rest.postJson(uri, reqObj, service.auth).on('complete', function(data, response) {
done(data, response);
});
};
return exported;
};
/* ./test/index.js */
var proxyquire = require('proxyquire');
var assert = require('chai').assert;
var restlerStub = {
postJson: function() {
return {
on: function(event, callback) {
callback('data', 'response');
}
}
}
}
var index = proxyquire('../src/index', {'restler': restlerStub})();
describe('index', function() {
it('should return the desired issue', function(done) {
var issue = index.getIssue('issueId', function(data, response) {
assert.equal(data, 'data');
assert.equal(response, 'response');
done();
})
});
});
/* ./package.json */
{
"scripts": {
"test": "mocha"
},
"dependencies": {
"restler": "^3.4.0"
},
"devDependencies": {
"chai": "^3.4.1",
"mocha": "^2.3.4",
"proxyquire": "^1.7.3"
}
}

Meteor client integration async testing with Velocity / Jasmine; How to get return value?

Updated: User count before and after signup still failing
Trying to test a new user signing up through the UI (see jQuery "signUp"). Users count from Method.call("usersCount") before and after signup both return 'undefined'.
I see 'undefined' -> user object -> 'undefined' in log. Not sure why user count is not getting assigned to the variable(s) in the spec code.
Second test checking the signup/logged-in users passes.
/tests/jasmine/client/integration/spec.js
// New user signup
function signUp (user, callback) {
$('.dropdown-toggle').trigger('click');
$('#signup-link').trigger('click');
$('#login-username').val(user.username);
$('#login-password').val(user.password);
$('#login-password-again').val(user.password);
$('#login-buttons-password').trigger('click');
callback;
}
describe('User signup', function() {
var user = { username: 'larry', password: 'password' };
beforeEach(function(done) {
Meteor.call("clearDB", done);
});
it('should increase users by one', function (done) {
var userCountBefore = Meteor.call("usersCount");
var userCountAfter = signUp(user, Meteor.call("usersCount"));
expect(userCountBefore + 1).toEqual(userCountAfter);
});
it('should automatically log-in new user', function () {
expect(Meteor.user().username).toEqual(user.username);
});
});
/packages/test-helpers.js (custom debug testing package; clearDB method from [https://gist.github.com/qnub/97d828f11c677007cb07][1])
if ((typeof process !== 'undefined') && process.env.IS_MIRROR) {
Meteor.methods({
usersCount: function () {
var count = Meteor.users.find({}).count();
return count;
},
clearDB: function(){
console.log('Clear DB');
var collectionsRemoved = 0;
var db = Meteor.users.find()._mongo.db;
db.collections(function (err, collections) {
// Filter out velocity and system.indexes from collections
var appCollections = _.reject(collections, function (col) {
return col.collectionName.indexOf('velocity') === 0 ||
col.collectionName === 'system.indexes';
});
// Remove each collection
_.each(appCollections, function (appCollection) {
appCollection.remove(function (e) {
if (e) {
console.error('Failed removing collection', e);
fut.return('fail: ' + e);
}
collectionsRemoved++;
console.log('Removed collection');
if (appCollections.length === collectionsRemoved) {
console.log('Finished resetting database');
}
});
});
});
console.log('Finished clearing');
}
});
};
Ok, this is one way to solve this:
it('should increase users by one', function (done) {
Meteor.call("usersCount", function(error, userCountBefore) {
signUp(user);
Meteor.call("usersCount", function (error, userCountAfter) {
expect(userCountAfter).toEqual(userCountBefore + 1);
done();
});
});
});
Future viewers, checkout the following links for reference/alternate approaches:
https://github.com/caolan/async
https://atmospherejs.com/peerlibrary/async
http://www.html5rocks.com/en/tutorials/es6/promises/
Thanks to #sanjo for helping me see the light!

How do I insert an object with Linq2IndexedDB

I'm experimenting with Linq2IndexedDB (v. 1.0.21) via unit tests (via Mocha), but I can't even make a simple insert work. What happens (when running under Google Chrome) is an internal exception is thrown on line 1535 of Linq2IndexedDB.js:
Uncaught TypeError: Cannot read property 'version' of undefined
My unit test code looks as follows; there's basically one test, "it can add objects":
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
_db = linq2indexeddb("test", null, true);
_db.deleteDatabase()
.done(function () {
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.linq.from("store").insert(refObj, "Key")
.done(function () {
console.log("Added object successfully");
done();
})
.fail(bindFail(done, "Inserting object failed"));
});
});
});
Am I doing something wrong here, or is there a bug in Linq2IndexedDB (or both)?
I've put up a corresponding test project on Github, complete with a Karma configuration, so you can run the included tests easily. The Karma configuration assumes you have Chrome installed.
I found a couple of issues:
I got the Linq2IndexedDB worker location wrong, it should be: '/base/lib/Linq2IndexedDB.js'
Inserting with the object with an out-of-line key doesn't work.
I eventually got insertion working on IE 10 and Chrome, although I'm still struggling with PhantomJS. To get it working under Chrome, I had to specify my schema explicitly, I suspect this is due to a bug in Linq2IndexedDB. My working solution is as follows:
Test:
"use strict";
define(["db", "linq2indexeddb", "chai", "underscore", "stacktrace"], function (db, linq2indexeddb, chai, _,
printStacktrace) {
var should = chai.should();
describe("db", function () {
var _db;
function fail(done, reason, err) {
console.log("err:", err);
if (typeof reason === "string") {
reason = new Error(reason);
}
if (!reason) {
console.log(typeof done, typeof reason);
reason = new Error("There's been an error, but no reason was supplied!");
var st = printStacktrace({e: reason});
console.log(st);
}
if (typeof done !== "function") {
throw new Error("Was not supplied a function for 'done'!");
}
done(reason);
}
// Bind done as the first argument to the fail function
function bindFail(done, reason) {
if (typeof done !== "function") {
throw new Error("done must be a function");
}
return _.partial(fail, done, reason);
}
beforeEach(function (done) {
// Linq2IndexedDB's web worker needs this URL
linq2indexeddb.prototype.utilities.linq2indexedDBWorkerFileLocation = '/base/lib/Linq2IndexedDB.js'
_db = new db.Database("test");
console.log("Deleting database");
_db.deleteDatabase()
.done(function () {
console.log("Initializing database");
_db.initialize()
.done(done)
.fail(bindFail(done, "Initializing database failed"));
})
.fail(bindFail(done, "Deleting database failed"));
});
it("can add objects", function (done) {
console.log("Starting test");
var refObj = {"key": "value"};
_db.insert(refObj)
.done(function () {
done();
})
.fail(bindFail(done, "Database insertion failed"));
});
});
});
Implementation:
define("db", ["linq2indexeddb"], function (linq2indexeddb) {
function getDatabaseConfiguration() {
var dbConfig = {
version: 1
};
// NOTE: definition is an array of schemas, keyed by version;
// this allows linq2indexedDb to do auto-schema-migrations, based upon the current dbConfig.version
dbConfig.definition = [{
version: 1,
objectStores: [
{ name: "store", objectStoreOptions: { keyPath: 'key' } },
],
defaultData: []
},
];
return dbConfig;
}
var module = {
Database: function (name) {
var self = this;
self._db = linq2indexeddb(name, getDatabaseConfiguration());
self.deleteDatabase = function () {
return self._db.deleteDatabase();
};
self.initialize = function () {
return self._db.initialize();
};
self.insert = function (data) {
return self._db.linq.from("store").insert(data);
};
}
};
return module;
});
EDIT:
The reason it wasn't working under PhantomJS was that I'd enabled debug logging in Linq2IndexedDB, for some reason this would seemingly clog up Karma's pipes at some point. After turning off debug logging, all configurations work.

Categories