How to test function calling multiple other functions? - javascript

I am writing an ExpressJS Middleware that modifies request object a bit and checks if user has access to that specific page. I have a problem with unit-testing it. I've written separate tests for every method except one: handler. How can I test handler function? Should I test it? Or should I just ignore it with istanbul ignore next as I've got every other function covered? Or maybe I should somehow rewrite my handler function to make it testable?
class Example {
constructor(request, response, next, userAccountService) {
this.req = request;
this.res = response;
this.next = next;
this.userAccountService = userAccountService;
}
removeTokenFromQuery() {
delete this.req.query.token;
}
isValidRequest() {
if (!this.req.secure) {
return false;
}
if (typeof this.req.query.token !== 'undefined') {
return false;
}
if (typeof this.req.query.unsupportedQueryParam !== 'undefined') {
return false;
}
return true;
}
isPageAccessibleForUser() {
return this.userAccountService.hasAccess('example');
}
async handler() {
this.removeTokenFromQuery();
if (!this.isValidRequest()) {
throw new Error('Invalid request');
}
if (!this.isPageAccessibleForUser()) {
this.res.statusCode(500);
this.res.end();
return;
}
this.next();
}
}
Then it's called as Express middleware:
this.app.use((res, req, next) => {
const exampleObj = new Example(res, req, next, userAccServ);
exampleObj.handler();
});

Should I test it?
Yes, based on your example handler contains (what looks to be) some critical business logic. It is responsible for orchestrating:
removing a token from a request (security)
determining if a request is valid (security/auth)
determining if a page is accessible for a user (security/auth)
If this function was not tested, a future engineer may make a modification to this important function, and they wouldn't receive any feedback on their change. Suppose that from human error they accidentally removed the isValidRequest check? or removed the !. However unlikely the risk associated with this happening may be catastrophic when compared to the relatively small amount of effort necessary to test this.
How can I test handler function?
The next question is how you actually test this :) I would opt to test this at the lowest "level" possible lower (unit tes this method by invoking it directly vs higher (going through the express framework).
As you mentioned, there are tests for the implementations of each one of the functions that handler delegates to, IMO the important thing to test in handler is the flow and NOT the implementations (since those are already well tested).
describe('handler()', () => {
it('removes token from query');
it('errors on invalid request');
it('returns 500 status code when page is inaccessible');
it('continues with .next() when request is valid and page is accessible');
})
In order to do this I would instantiate Example and then patch the methods necessary in order to create the correct flow for your handler() tests. So for the invalid request test this might look like:
const example = new Example();
sinon.stub(example, "isValidRequest").returns(false);
If this isn't stubbed than these tests essentially duplicate the other tests (by testing the actual implementation). Using stubs allows for the implementation of isValidRequest to change while still having unit test protection in handler

Related

Is it possible to test a js function which returns nothing with chai?

async executeEventEndSuccess(statusData) {
if (storageType.toUpperCase() !== common.Blob) {
let appStatus = status.getStatus(statusData.tenantId, statusData.viewerId, statusData.excutionId);
uploadHelper.renameFileOnEndEvent(statusData.tenantId, statusData.viewerId, statusData.excutionId);
appStatus.forEach(async ele => {
if (ele.executionID === parseInt(statusData.excutionId) && ele.tenant === statusData.tenantId && ele.panel === statusData.viewerId && ele.tableName === common.type.panelWatch) {
try {
const updateDataPanelStatus = await panelStatusData.updatePanelStatus(statusData.tenantId, statusData.viewerId, ele.path)
eventRepo.updatePanelStatus(statusData.tenantId, statusData.viewerId, updateDataPanelStatus);
}
catch (error) {
throw error;
}
};
});
}
try {
console.log(statusData.tenants);
await eventRepo.executeEndProcedure(statusData);
}
catch (error) {
throw error;
}
}
Here i need to test whether appStatus is existing in my executeEventEndSuccess function or not? I need to perform unit testing for this function??
Short answer: Yes I would personally perform unit testing for this function.
And Yes it's possible to hook into this function in some backwards ways, through tools like mock-require or other test/design hacks.
It looks like this function does a number of important things that would benefit from test coverage, in order to verify that it works, as well as protect against regressions:
Rename File if not a common.Blob
if (storageType.toUpperCase() !== common.Blob) {
...
uploadHelper.renameFileOnEndEvent(
statusData.tenantId,
statusData.viewerId,
statusData.excutionId
);
Performs status matching
if (ele.executionID === parseInt(statusData.excutionId) && ele.tenant === statusData.tenantId && ele.panel === statusData.viewerId && ele.tableName === common.type.panelWatch) {
Performs (distributed?) status updates
const updateDataPanelStatus = await panelStatusData.updatePanelStatus(statusData.tenantId, statusData.viewerId, ele.path)
eventRepo.updatePanelStatus(statusData.tenantId, statusData.viewerId, updateDataPanelStatus);
Performs end procedure
await eventRepo.executeEndProcedure(statusData);
Each one of these actions seems pretty important!
consider the case of the status matching. Without tests, suppose you verify everything manually, click through the transaction ensure everything is working. A couple months later another engineer comes back, doesn't think that ele.tenant is used anymore and removes the check from the conditional. Without tests, there is no feedback on this change, the feature isn't protected, the client isn't protected and the engineer isn't protected!
Additionally, consider the renaming feature. That seems pretty important, if I was working in this code base I would personally like tests covering that this feature is working, and be protected against introducing a regression that might cause renames not to occur.
Finally, Tests will help you verify the features and flows work during development, and will do so with less overhead and quicker than manually executing these flows.
The hard part is now actually testing it :) As it stands its difficult to isolate the important looking pieces of functionality I listed above. You could refactor to help expose that functionality and make it directly testable, or you could use some sort of mocking/patch framework to allow you to hook into the functionality and assert on calls.

How to increase the code coverage using istanbul in node.js

I am using Istanbul for code coverage, but i m getting very low coverage percentage particularly in Models file.
Consider the following is the model file:
ModelA.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var app = require('../server')
var db = require('../db/dbConnection');
var config = require('../configs/config')
const Schema1 = new Schema({ 'configurations': [] });
exports.save = function (aa, data, callback) {
var logMeta = {
file: 'models/modelA',
function: 'save',
data: {},
error: {}
}
if (!aa) {
return callback('aa is required')
}
global.logs[aa].log('info', 'AA: ' + aa, logMeta);
db.connectDatabase(aa, function(error, mongoDB){
if(error){
logMeta.data['error'] = error
global.logs[aa].log('error', 'error', logMeta);
return callback(error)
}
const ModelA = mongoDB.model('bbb', cccc);
ModelA.findOneAndUpdate({}, data, {upsert: true, new: true, runValidators: true}, function(error ,result){
if (error) {
logMeta.data['error'] = error
global.logs[aa].log('error', 'error', logMeta);
}
else {
logMeta.data = {}
logMeta.data['result'] = JSON.parse(JSON.stringify(result))
global.logs[aa].log('info', 'result', logMeta);
}
callback(error, result);
});
})
}
TestA.js:
var should = require('should'),
sinon = require('sinon'),
ModelA= require("../models/ModelA");
describe('Model test', function () {
it('Should save Model', function (done) {
var todoMock = sinon.mock(new ModelA({'configurations': []}));
var todo = todoMock.object;
todoMock
.expects('save')
.yields(null, 'SAVED');
todo.save(function(err, result) {
todoMock.verify();
todoMock.restore();
should.equal('SAVED', result, "Test fails due to unexpected result")
done();
});
});
});
But i am getting codecoverage percentage 20. SO how can i increase the percentage:
ALso:
1.Whether i have to mock the db.connectDatabase if yews how can i acheive that?
Whether i have to use TestDb to run all my UnitTest? Or i have to assert??
Code Coverage will work for Unit Test or integration test???
Please share your ideas. Thanks
I have been using Istanbul to 100% code cover most of my client/server projects so I might have the answers you are looking for.
How does it work
Whenever you require some local file, this gets wrapped all over the place to understand if every of its parts is reached by your code.
Not only the required file is tainted, your running test is too.
However, while it's easy to code cover the running test file, mocked classes and their code might never be executed.
todoMock.expects('save')
Accordingly to Sinon documentation:
Overrides todo. save with a mock function and returns it.
If Istanbul tainted the real save method, anything within that scope won't ever be reached so that you are actually testing that mock works, not that your real code does.
This should answer your question: Code Coverage will work for Unit Test or integration test ???
The answer is that it covers the code, which is the only thing you're interested from a code cover perspective. Covering Sinon JS is nobody goal.
No need to assert ... but
Once you've understood how Istanbul works, it follows up naturally to understand that it doesn't matter if you assert or not, all it matters is that you reach the code for real and execute it.
Asserting is just your guard against failures, not a mechanism interesting per se in any Istanbul test. When your assertion fails, your test does too, so it's good for you to know that things didn't work and there's no need to keep testing the rest of the code (early failure, faster fixes).
Whether you have to mock the db.connectDatabase
Yes, at least for the code you posted. You can assign db as generic object mock to the global context and expect methods to be called but also you can simplify your life writing this:
function createDB(err1, err2) {
return {
connectDatabase(aa, callback) {
callback(err1, {
model(name, value) {
return {
findOneAndUpdate($0, $1, $3, fn) {
fn(err2, {any: 'object'});
}
};
}
});
}
};
}
global.db = createDB(null, null);
This code in your test file can be used to create a global db that behaves differently accordingly with the amount of errors you pass along, giving you the ability to run the same test file various times with different expectations.
How to run the same test more than once
Once your test is completed, delete require.cache[require.resolve('../test/file')] and then require('../test/file') again.
Do this as many times as you need.
When there are conditional features detection
I usually run the test various times deleting global constructors in case these are patched with a fallback. I also usually store them to be able to put 'em back later on.
When the code is obvious but shouldn't be reached
In case you have if (err) process.exit(1); you rarely want to reach that part of the code. There are various comments understood by Istanbul that would help you skip parts of the test like /* istanbul ignore if */ or ignore else, or even the generic ignore next.
Please consider thinking twice if it's just you being lazy, or that part can really, safely, be skipped ... I got bitten a couple of times with a badly handled error, which is a disaster since when it happens is when you need the most your code to keep running and/or giving you all the info you need.
What is being covered?
Maybe you know this already but the coverage/lcov-report/index.html file, that you can open right away with any browser, will show you all the parts that aren't covered by your tests.

before() not executing before subsequent describes()?

Given the following code :
var api = {};
var models = {};
describe(vars.project.name, function() {
before(function(done) {
// Loading models
models_module.getDbInstance('0.1', function(res) {
db = res;
var config = {
server: server,
db: db,
v: '0.1'
};
// Importing all the tests
api.base = require('./functions/api.base.js')(config);
models.test_table = require('./functions/models.test_table.js')(config);
done();
});
});
// Tests general status
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', api.base.status.without_route);
});
});
This loads the database with my models for version 0.1 and then require my tests definitions, passing it the database and some other config infos. This is in theory.
Instead, I get an error saying Cannot read property 'status' of undefined. This means that it tries to execute my tests, or at least initialize it, before completing the before function.
I also tried with async.series (loading the models, then loading the tests) but it doesn't do anything, only displays 0 passing (0ms).
What's wrong with this?
I saw the tree and missed the forest...
This cannot work:
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', api.base.status.without_route);
});
The problem is that you are trying to evaluate api.base while Mocha is constructing the test suite. In brief, you should keep in mind that the callbacks that are passed to describe are evaluated before the tests start. The callbacks passed to the it calls are not evaluated until the individual tests are executed. (See another answer of mine for all the gory details of what happens when.) So at the stage where api.base.satus.without_route is evaluated, api.base has not been set.
describe('checking the status of the API without a version', function() {
it('should succeed with 200 OK', function () {
api.base.status.without_route();
});
});
Given the code you had in your question, I've assumed that api.base.status.without_route is a function which would fail if your conditions are not met. That it be a function looks peculiar to me but I don't know the larger context of your application. I'm basing my assumption on the fact that the 2nd argument passed to it should be a callback. If it is not a function, then you'll need to write whatever specific test you need (if (api.base.status.without_route === ...) etc.)

How to use methods containing mongoose functions with control flow frameworks (e.g. Async or Step)

I am struggling a little with how to call asynchronous functions in a serial manner. In particular, functions which incorporate mongoose calls to the database. I have a class definition which includes two methods: (MovieFile.exists) checks if a certain record exists in the database; (MovieFile.save) saves a record in the database. Ideally I want to be able to save the record (MovieFile.save()) after confirming whether or not it exists in the database (MovieFile.exists()).
Ideally I want to do something like this:
// Create instance of MovieFile object.
var movieFile = new MovieFile(mongoose);
if (movieFile.exists() === false) {
movieFile.save();
}
Unfortunately the asynchronous nature on mongoose makes this not possible. I have been playing around with Step and Async control flow frameworks. Unfortunately I just can't get my head around how to use such frameworks in this case. I would be grateful if someone could tell me how to put the above code into either Step or Async. I think the issue is that the asynchronous mongoose calls are themselves embedded within a method (See: MovieFile.exists and MovieFile.save). I realise that use of such a framework may be overkill in this example, but I am trying to use this as a learning exercise.
function MovieFile(mongoose) {
var Movie = mongoose.model('Movie');
this.exists = function() {
// Confirm necessary object variables have been set.
if (typeof this.originalFileName === 'undefined') {
throw error = new Error('Variable originalFilename has not been set for MovieFile.');
}
if (typeof this.fileSize !== 'number') {
throw error = new Error('Variable originalFilename has not been set for MovieFile.');
}
// Check database for existing record.
Movie
.find({ originalFileName: this.originalFileName, size: this.fileSize })
.exec(function(error, results) {
if (error) {
throw error;
}
else if (results.length === 0) {
return false;
}
else if (results.length === 1) {
return true;
}
else {
throw error = new Error('More than one record for this movie record exists.');
}
});
};
this.save = function() {
// save movie to database.
var values = {
name: this.getName(),
machineFileName: this.getMachineFileName(),
originalFileName: this.getName(),
size: this.getFileSize(),
};
var movie = new Movie(values);
movie.save(function(error, data) {
if (error) {
console.log(error);
}
else {
console.log('Movie saved.');
}
});
};
};
You have to pass a callback to your exists function, wich will return the boolean true or false.
Exemple :
this.exists = function(callback) {
// your code
callback(true);
});
Then :
movieFile.exists(function(exists) {
if (!exists) boomovieFile.save();
});
As you said, this is due to the asynchronous mongoose calls. So, you will have to replace your return statements by callbacks.
I've never used Step or Async control flow frameworks. But I think, you don't need to rely on those modules for this kind of simple case. In my opinion, you should only consider using those modules when you will have to deal with a lot of callbacks.
Edit
As I'm new to control flow frameworks, I've found this article which describes well the concept and shows how to build your own. That's great for learning purposes.
This article will give you an example on how to manage a queue using the async module.
This post list some other control flow modules, with few examples.
The first answer also show how to avoid nested callbacks, by decoupling on small functions. This is how I personnaly manage my apps, which works quite well in my case.
But I would be curious to hear more about control flow techniques, so let me know if you found great ressources.

URL routing in Node.js

Homework done:
The Node Beginner Book
How do I get started with Node.js [closed]
Structuring handlers in Node
Backstory: I wanted to try and write my own framework but I'm running into some troubles, most likely due to not understanding it fully.
What I want to achieve is a syntax that looks like this:
var app = require('./app'); //this part is understood and it works in my current code.
app.get('/someUrl', function(){ //do stuff here });
app.post('/someOtherUrl', function(){ //do stuff here });
I know of the Express-framework that has this same syntax but reading their source code still eludes me.
This might be a trivial task to achieve but I simply can't produce it, yet.
Trying to require('./app'); in a file deeper in the application produces a undefined object, so I'm guessing that a server is a singleton object.
So what have I tried?
My current code looks like this, and somehow I feel like this is the way to go, but I can't apparently do it like this.
I'm omitting all the require(); statements to keep it more readable.
server.js:
var app = module.exports = {
preProcess: function onRequest(request, response){
processor.preRequest(request); //this object adds methods on the request object
var path = urllib.parse(request.url).pathname;
router.route(urls, path, request, response);
},
createServer: function() {
console.log("Server start up done.");
return this.server = http.createServer(this.preProcess);
}
};
exports.app = app;
At the time of writing I'm experimenting with extending this object with a get() method.
index.js:
var app = require('./server');
app.createServer().listen('1337');
the router.route() bit basically sends the request onward into the application and inside the router.js-file I do some magic and route the request onward to a function that maps (so far) to the /urlThatWasRequested
This is the behavior I'd like to leave behind.
I know this might be a pretty tall order but all my code is easily discardable and I'm not afraid of rewriting the entire codebase as this is my own project.
I hope this is sufficient in explaining my question otherwise, please say what I should add to make this a bit more clear.
Thanks in advance!
I'm not exactly sure what your question is, but here's some thoughts:
1) You are creating a circular reference here:
var app = module.exports = {
// some other code
}
exports.app = app;
You add app as a property of app. There's no need for the last line.
2) You need to hold handlers in app. You can try something like this:
var app = module.exports = {
handlers : [],
route : function(url, fn) {
this.handlers.push({ url: url, fn: fn });
},
preProcess: function onRequest(request, response){
processor.preRequest(request);
var path = urllib.parse(request.url).pathname;
var l = this.handlers.length, handler;
for (var i = 0; i < l; i++) {
handler = this.handlers[i];
if (handler.url == path)
return handler.fn(request, response);
}
throw new Error('404 - route does not exist!');
},
// some other code
}
Note that you may alter this line: if (handler.url == path) in such a way that handler.url is a regular expression and you test path against it. Of course you may implement .get and .post variants, but from my experience it is easier to check whether a request is GET or POST inside the handler. Now you can use the code above like this:
app.route('/someUrl', function(req, res){ //do stuff here });
The other thing is that the code I've shown you only fires the first handler for a given URL it matches. You would probably want to make more complex routes involving many handlers (i.e. middlewares). The architecture would be a bit different in that case, for example:
preProcess: function onRequest(request, response){
var self = this;
processor.preRequest(request);
var path = urllib.parse(request.url).pathname;
var l = self.handlers.length,
index = 0,
handler;
var call_handler = function() {
var matched_handler;
while (index < l) {
handler = self.handlers[index];
if (handler.url == path) {
matched_handler = handler;
break;
}
index++;
}
if (matched_handler)
matched_handler.fn(request, response, function() {
// Use process.nextTick to make it a bit more scalable.
process.nextTick(call_handler);
});
else
throw new Error('404: no route matching URL!');
};
call_handler();
},
Now inside your route you can use
app.route('/someUrl', function(req, res, next){
//do stuff here
next(); // <--- this will take you to the next handler
});
which will take you to another handler (or throw exception if there are no more handlers).
3) About these words:
Trying to require('./app'); in a file deeper in the application produces a undefined
object, so I'm guessing that a server is a singleton object.
It isn't true. require always returns the reference to the module object. If you see undefined, then you've messed up something else (altered the module itself?).
Final note: I hope it helps a bit. Writing your own framework can be a difficult job, especially since there already are excelent frameworks like Express. Good luck though!
EDIT
Implementing .get and .set methods is actually not difficult. You just need to alter route function like this:
route : function(url, fn, type) {
this.handlers.push({ url: url, fn: fn, type: type });
},
get : function(url, fn) {
this.route(url, fn, 'GET');
},
post : function(url, fn) {
this.route(url, fn, 'POST');
},
and then in routing algorithm you check whether type property is defined. If it is not then use that route (undefined type means: always route). Otherwise additionally check if a request's method matches type. And you're done!

Categories

Resources