I am new to mock concept and javascript programming either. I want to to mock pg (postgres module) in the javascript program. I can imitate very simple scenario, but in actual I don't.
Here is my userHandler.js:
var pg = require('pg');
var connectionString = process.env.DATABASE_URL || 'postgres://admin:admin#localhost:5432/mydb';
exports.handlePost = function(req,res){
var results = [];
// Grab data from http request
var adata = [req.body.Username, ..., req.body.FaxNum]; //Ignore for short.
// Get a Postgres client from the connection pool
pg.connect(connectionString, function(err, client, done) {
// SQL Query > Insert Data
var func_ = 'SELECT Dugong.Users_Add($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)';
var addUser_ = client.query(func_, adata);
addUser_.on('error', function(error){
var data = {success : false,
username : req.body.Username,
reason : {errmsg : error.detail,
errid : 'addUser_' }};
return res.json(data);
});
addUser_.on('end',function(result){
var data = {success : true, username : req.body.Username};
console.log('Insert record completed');
return res.json(data);
});
// Handle Errors
if(err) {
console.log(err);
return ;
}
return;
});
};
And here is my unit test file. m_users_page.js:
var express = require('express');
var router = express.Router();
var test = require('unit.js');
var mock = require('mock');
var httpMocks = require('node-mocks-http');
var real_users_page = require('../routes/users_page.js');
var b = mock("../routes/userHandler.js", {
pg: {
connect: function (connectionString,callback) {
if(connectionString === 'postgres://admin:admin#localhost:5432/skorplusdb'){
console.log('333');
//pseudo object
var client = {query : function(func_, adata, cb){
cb(null,adata);
}};
client.on('error', 'test emit the error in my mock unit.');
//pseudo done object
var done = function(){};
callback(null, client, done);
return ;
}
}
}
}, require);
describe('Test with static login', function(){
it('Test simple login', function(done){
var request = httpMocks.createRequest({
method: 'POST',
url: '/users',
body: { Username:"Je", ..., FaxAreaCode:'232'} //Ignore for short
});
var response = httpMocks.createResponse();
b.handlePost(request,response, function(){
var data = response._getData();
console.log("7777777777" + data);
done();
});
});
});
Here is the error :
$ mocha testing/m_users_page.js
Test with static login
333
1) Test simple login
0 passing (7ms)
1 failing
1) Test with static login Test simple login:
TypeError: Object #<Object> has no method 'on'
at Object.mock.pg.connect (testing/m_users_page.js:22:14)
at Object.exports.handlePost (routes/userHandler.js:30:6)
at Context.<anonymous> (testing/m_users_page.js:63:5)
My questions are:
What is a proper way to do a unit test in Node + Express + Mock + node-mocks-http?
How to find good framework with well document I must read. After several days, I started to circling around the result from search engines. They are too simple, I can't adapt it to my problem.
First, make sure you understand the difference between unit tests and integration tests. If you want to test against the actual db, even if it has a dummy data set, that's an integration test and it doesn't need a mock: just connect to the database with the dummy data.
But suppose you want to test your webserver module, and you want to mock the db. First, pass the database module as a parameter rather than requiring pg directly. Also, wrap the postgres interface with your own class:
const { Pool } = require('pg');
module.exports = class DatabaseInterop {
// Connection parameters can be passed to the constructor or the connect method, parameters to
// DatabaseInterop::connect will override the initial constructor parameters.
constructor ({
user,
password,
database,
host,
logger={log: console.log, err: console.error},
}) {
this.logger = logger;
this._params = {
user,
password,
database,
host,
};
}
connect (params) {
const {
user,
password,
database,
host,
} = Object.assign({}, this._params, params);
this._pool = new Pool({
user,
password,
database,
host,
});
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach(function (sig) {
process.on(sig, async () => {
logger.log(`Exiting for ${sig}...`);
process.exit(0);
});
});
return this;
}
async stop () {
return this._pool.end();
}
runQuery (queryString, params=[]) {
return params.length ? this._pool.query(queryString, params) : this._pool.query(queryString);
}
};
Now to mock it out, you can simply extend your custom class in your test file:
const DatabaseInterop = require('/path/to/database_interop.js');
class MockDB extends DatabaseInterop {
connect () {
// no-op
}
runQuery (qs, ...params) {
// return whatever
}
stop () {
// noop
}
}
Now for your tests you can inject the mock and your actual system inject the actual interface.
Related
I have a controller which I want to test
// user model
const userModel = require('../models/user')
// get user model from handler
const User = userModel.User
function index(req, res) {
User.find(function(err, users) {
if (err) console.log(err)
console.log('debug')
res.render('../views/user/index', {
title: 'User index',
users: users
})
})
}
module.exports = {
index
}
using the following code
// dependencies
const sinon = require('sinon')
// controller to be tested
const controller = require('../controllers/user')
// get user test
describe('test user index', function() {
it ('should return users', function() {
var req = {}
var res = {
render: function() {
sinon.spy()
}
}
controller.index(req, res)
})
})
When I run the test it doesn't execute the console.log('debug') which seems to me indicates that the test can't run the User.find() function. Any help would be appreciated.
As a temporary solution, you can include DB connection string at the top of the file.
Explanation: I guess your controller is part of an app, so it connects to DB on init. However, here you are testing an isolated controller, therefore DB connection isn't initialized yet. That's quick solution, for proper way of doing that refer to official docs
I am trying to modify a Node.js function called 'splunk-logger'. The problem is that when the SNS Message comes into the function, the events from the Anti-Virus (Trend Micro DeepSecurity) console are grouped together. I already contacted their support and they said this is just the way events are sent and they can't help.
Example: {Message {Event_1} {Event_2} {Event_3}}
Now the JavaScript function works great and the events are forwarded to Splunk. However, since they are grouped together BEFORE they even hit the Lambda function, Splunk sees them as 1 single event instead of 3.
My thought is to take the 'event' variable (since it contains the sns 'message') and parse through that to separate each event (probably using regex or something). Then, I can either create another function to send each event immediately or simply call the "logger.flushAsync" function to send them.
Link to splunk-dev explaining the funciton: http://dev.splunk.com/view/event-collector/SP-CAAAE6Y#create.
Here is the code from the index.js:
const loggerConfig = {
url: process.env.SPLUNK_HEC_URL,
token: process.env.SPLUNK_HEC_TOKEN,
};
const SplunkLogger = require('./lib/mysplunklogger');
const logger = new SplunkLogger(loggerConfig);
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
// Log JSON objects to Splunk
logger.log(event);
// Send all the events in a single batch to Splunk
logger.flushAsync((error, response) => {
if (error) {
callback(error);
} else {
console.log(`Response from Splunk:\n${response}`);
callback(null, event.key1); // Echo back the first key value
}
});
};
Here is the code from the mysplunklogger.js file.
'use strict';
const url = require('url');
const Logger = function Logger(config) {
this.url = config.url;
this.token = config.token;
this.addMetadata = true;
this.setSource = true;
this.parsedUrl = url.parse(this.url);
// eslint-disable-next-line import/no-dynamic-require
this.requester = require(this.parsedUrl.protocol.substring(0, this.parsedUrl.protocol.length - 1));
// Initialize request options which can be overridden & extended by consumer as needed
this.requestOptions = {
hostname: this.parsedUrl.hostname,
path: this.parsedUrl.path,
port: this.parsedUrl.port,
method: 'POST',
headers: {
Authorization: `Splunk ${this.token}`,
},
rejectUnauthorized: false,
};
this.payloads = [];
};
// Simple logging API for Lambda functions
Logger.prototype.log = function log(message, context) {
this.logWithTime(Date.now(), message, context);
};
Logger.prototype.logWithTime = function logWithTime(time, message, context) {
const payload = {};
if (Object.prototype.toString.call(message) === '[object Array]') {
throw new Error('message argument must be a string or a JSON object.');
}
payload.event = message;
// Add Lambda metadata
if (typeof context !== 'undefined') {
if (this.addMetadata) {
// Enrich event only if it is an object
if (message === Object(message)) {
payload.event = JSON.parse(JSON.stringify(message)); // deep copy
payload.event.awsRequestId = context.awsRequestId;
}
}
if (this.setSource) {
payload.source = `lambda:${context.functionName}`;
}
}
payload.time = new Date(time).getTime() / 1000;
this.logEvent(payload);
};
Logger.prototype.logEvent = function logEvent(payload) {
this.payloads.push(JSON.stringify(payload));
};
Logger.prototype.flushAsync = function flushAsync(callback) {
callback = callback || (() => {}); // eslint-disable-line no-param-reassign
console.log('Sending event(s)');
const req = this.requester.request(this.requestOptions, (res) => {
res.setEncoding('utf8');
console.log('Response received');
res.on('data', (data) => {
let error = null;
if (res.statusCode !== 200) {
error = new Error(`error: statusCode=${res.statusCode}\n\n${data}`);
console.error(error);
}
this.payloads.length = 0;
callback(error, data);
});
});
req.on('error', (error) => {
callback(error);
});
req.end(this.payloads.join(''), 'utf8');
};
module.exports = Logger;
import requests
import re
import json
import os
def lambda_handler(event, context):
data = json.dumps(event)
EventIds = re.findall(r'{\\\".+?\\\"}', data)
EventLength = len(EventIds)
headers = {'Authorization': 'Splunk ' + os.environ['SPLUNK_HEC_TOKEN']}
i = 0
while i < EventLength:
response = requests.post(os.environ['SPLUNK_HEC_URL'], headers=headers, json={"event":EventIds[i]}, verify=True)
i+=1
Arrays are the data type used when Deep Security 10.0 or newer sends events to Amazon SNS. But Splunk wants one event per message. So don't send the array directly.
Instead, use the Splunk logger or Lambda to iterate through the array, sending each item as an individual message. You can modify this sample Lambda script for Node.js:
https://github.com/deep-security/amazon-sns/blob/master/lambda-save-ds-event-to-s3.js
It sends events to S3 individually (which is what you need). Just change it to send to Splunk instead.
Disclosure: I work for Trend Micro.
I would like to stub the save method available to Mongoose models. Here's a sample model:
/* model.js */
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
username: {
type: String,
required: true
}
});
var User = mongoose.model('User', userSchema);
module.exports = User;
I have some helper function that will call the save method.
/* utils.js */
var User = require('./model');
module.exports = function(req, res) {
var username = req.body.username;
var user = new User({ username: username });
user.save(function(err) {
if (err) return res.end();
return res.sendStatus(201);
});
};
I would like to check that user.save is called inside my helper function using a unit test.
/* test.js */
var mongoose = require('mongoose');
var createUser = require('./utils');
var userModel = require('./model');
it('should do what...', function(done) {
var req = { username: 'Andrew' };
var res = { sendStatus: sinon.stub() };
var saveStub = sinon.stub(mongoose.Model.prototype, 'save');
saveStub.yields(null);
createUser(req, res);
// because `save` is asynchronous, it has proven necessary to place the
// expectations inside a setTimeout to run in the next turn of the event loop
setTimeout(function() {
expect(saveStub.called).to.equal(true);
expect(res.sendStatus.called).to.equal(true);
done();
}, 0)
});
I discovered var saveStub = sinon.stub(mongoose.Model.prototype, 'save') from here.
All is fine unless I try to add something to my saveStub, e.g. with saveStub.yields(null). If I wanted to simulate an error being passed to the save callback with saveStub.yields('mock error'), I get this error:
TypeError: Attempted to wrap undefined property undefined as function
The stack trace is totally unhelpful.
The research I've done
I attempted to refactor my model to gain access to the underlying user model, as recommended here. That yielded the same error for me. Here was my code for that attempt:
/* in model.js... */
var UserSchema = mongoose.model('User');
User._model = new UserSchema();
/* in test.js... */
var saveStub = sinon.stub(userModel._model, 'save');
I found that this solution didn't work for me at all. Maybe this is because I'm setting up my user model in a different way?
I've also tried Mockery following this guide and this one, but that was way more setup than I thought should be necessary, and made me question the value of spending the time to isolate the db.
My impression is that it all has to do with the mysterious way mongoose implements save. I've read something about it using npm hooks, which makes the save method a slippery thing to stub.
I've also heard of mockgoose, though I haven't attempted that solution yet. Anyone had success with that strategy? [EDIT: turns out mockgoose provides an in-memory database for ease of setup/teardown, but it does not solve the issue of stubbing.]
Any insight on how to resolve this issue would be very appreciated.
Here's the final configuration I developed, which uses a combination of sinon and mockery:
// Dependencies
var expect = require('chai').expect;
var sinon = require('sinon');
var mockery = require('mockery');
var reloadStub = require('../../../spec/utils/reloadStub');
describe('UNIT: userController.js', function() {
var reportErrorStub;
var controller;
var userModel;
before(function() {
// mock the error reporter
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false,
useCleanCache: true
});
// load controller and model
controller = require('./userController');
userModel = require('./userModel');
});
after(function() {
// disable mock after tests complete
mockery.disable();
});
describe('#createUser', function() {
var req;
var res;
var status;
var end;
var json;
// Stub `#save` for all these tests
before(function() {
sinon.stub(userModel.prototype, 'save');
});
// Stub out req and res
beforeEach(function() {
req = {
body: {
username: 'Andrew',
userID: 1
}
};
status = sinon.stub();
end = sinon.stub();
json = sinon.stub();
res = { status: status.returns({ end: end, json: json }) };
});
// Reset call count after each test
afterEach(function() {
userModel.prototype.save.reset();
});
// Restore after all tests finish
after(function() {
userModel.prototype.save.restore();
});
it('should call `User.save`', function(done) {
controller.createUser(req, res);
/**
* Since Mongoose's `new` is asynchronous, run our expectations on the
* next cycle of the event loop.
*/
setTimeout(function() {
expect(userModel.prototype.save.callCount).to.equal(1);
done();
}, 0);
});
}
}
Have you tried:
sinon.stub(userModel.prototype, 'save')
Also, where is the helper function getting called in the test? It looks like you define the function as the utils module, but call it as a method of a controller object. I'm assuming this has nothing to do with that error message, but it did make it harder to figure out when and where the stub was getting called.
In my app, I do return promise with my mongoose model:
var roomModel = require('../../../models/room').roomModel;
roomModel.findOne({ name: req.body.roomName })
.then(
(room) => {
return new Promise(function(resolve, reject) {
//if no room present, create one, if present, check password
if (room) {
if (room.password === req.body.roomPassword) {
return resolve(room);
} else {
return reject({
code: 401,
message: 'Room password not correct'
});
}
} else {
// create new room with given data
var newRoom = roomModel({});
newRoom.name = req.body.roomName;
newRoom.password = req.body.roomPassword;
//newRoom.users = [];
newRoom.users[0] = {
name: req.body.userName
};
newRoom.save()
.then((data) => {
console.log(data);
if (!data) {
return reject({
code: 500,
message: 'Error when saving room'
});
} else {
return resolve(newRoom);
}
});
}
});
}
)
.then((room) => {
room.findOne({ 'users.name': req.body.userName })
.then((user) => {
console.log(user);
});
})
room.js model:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = require('./user').userSchema;
var room = new Schema({
name: String,
password: String,
users: [userSchema]
});
module.exports.roomSchema = room;
module.exports.roomModel = mongoose.model('room', room);
users.js model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var user = new Schema({
name: String
});
module.exports.userSchema = user;
module.exports.userModel = mongoose.model('user', user);
but when I try to call .findOne() function on this returned model, I get following error:
TypeError: room.findOne is not a function
is model passed in the promise not a model in next .then() statement?
Found problem by myself: I was passing not the model, on which I can use find operations, but document, on which I can perform save options (not find, since it's not a model).
Well, as the docs say "queries are not promises".
There's even a findOne() example in there...
Change your code to
roomModel.findOne({ name: req.body.roomName }).exec().then(/* your stuff */)
and you may have more luck.
You missed out exec() method in your query, try to use and get resolved.
roomModel.find({ name: req.body.roomName }).exec().then(/* your stuff */)
I should think what you're doing is dangerous. Calling a queries then() multiple times might
lead to multiple query calls.
https://mongoosejs.com/docs/queries.html#queries-are-not-promises
Also, there is no need to do exec().then(). Just calling then() executes the query; a better way to use exec() is to actually pass a callback to it.
in my case it happened that no matter how I exported it it wasn't working, and I found this solution:
var {roomModel} = require('../../../models/room')
Put the import within {}
and export it normally, i do like this:
var room = mongoose.model('room',roomSchema);
module.exports.room = room;
module.exports = function JsonOutputHook() {
var Cucumber = require('cucumber');
var JsonFormatter = Cucumber.Listener.JsonFormatter();
var fs = require('fs');
JsonFormatter.log = function (json) {
fs.writeFile('../reports/cucumberReport.json', json, function (err) {
if (err) throw err;
console.log('json file location: ../reports/cucumberReport.json');
});
};
this.registerListener(JsonFormatter);
};
I am using this code to generate json, but cucumber steps are not updating the pass fail status.
Though the test case got failed it shows result as Pass. And json is also not getting updated though I am adding a hook after execution.
Please help me so I can run cucumber-js with protractor to get json. I want to generate correct report.
This is my scenario: Login successful with valid account
Given I go on "http://store.demoqa.com/products-page/your-account/"
When I input user and password
Then I should see "Logout" link
Step definition file
'use strict';
var myStepDefinitionsWrapper = function () {
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;
this.Given(/^I go on "([^"]*)"$/,{timeout: 60 * 1000},function (arg1) {
browser.driver.get(arg1);
browser.manage().timeouts().pageLoadTimeout(10000);
});
this.When(/^I input user and password$/,{timeout: 60 * 1000}, function (callback) {
console.log("000000000000");
browser.sleep(5000);
browser.wait(function() {
var login = by.id('log');
return browser.driver.isElementPresent(login);
}, 30000);
browser.driver.findElement(by.id('log')).sendKeys("pratand");
browser.driver.findElement(by.id('pwd')).sendKeys("cygent#india11");
browser.driver.findElement(by.id('login')).click();
callback();
});
this.Then(/^I should see "([^"]*)" link$/,{timeout: 60 * 1000},function (arg1,callback) {
var logoutpath = by.xpath('//div[#id="account_logout"]/a');
browser.wait(function() {
return browser.driver.isElementPresent(logoutpath);
}, 30000);
expect(browser.driver.findElement(logoutpath).getText()).to.eventually.equal(arg1).and.notify(callback());
// expect(true).toEqual(true);
browser.driver.isElementPresent(logoutpath).then(function(isPresent){
browser.driver.findElement(logoutpath).then(function(start){
start.click();
});
});
});
};
module.exports = myStepDefinitionsWrapper;
Try to set it in configuration file :
cucumberOpts: {
format: 'json:e2e-reports/json/results.json',}
And create such as directory for saving json :
beforeLaunch: () => {
const jsonReports = path.join(process.cwd(), '/e2e-reports/json');
const htmlReports = path.join(process.cwd(), '/e2e-reports/html');
if (!fs.existsSync(jsonReports)) {
mkdirp.sync(jsonReports);
}
if (!fs.existsSync(htmlReports)) {
mkdirp.sync(htmlReports);
}
}