When attempting to meet the specification set by unit tests supplied, when attempting to return the status code for a method I am hit with
TypeError: res.status is not a function
when running the function createUser in the API implementation. It happens with every method call, such as res.send, res.sendStatus etc. Even if I add res.status() to the testing to set it there, it returns the same error.
apiTests.js
let chai = require('chai');
let expect = chai.expect;
let sinon = require('sinon');
let sinonChai = require('sinon-chai');
chai.use(sinonChai);
let model = require('../tron_model.js'); // for stubbing only
let api = require('../tron_api.js');
describe('API', function() {
describe('creating users', function() {
it('should return 201 on creating user', function () {
let mockModel = sinon.stub(new model.TronModel());
mockModel.addUser.onFirstCall().returns(new model.User('user','pass'));
let req = {body: {username: 'goatzilla', password: 'x'}};
let res = {end: function(){}};
api.init(mockModel);
api.createUser(req, res);
expect(res.statusCode).to.equal(201);
expect(mockModel.addUser).to.have.callCount(1);
});
});
});
tron_api.js
let model = undefined;
let api = exports;
api.init = function(modelArg) {
model = modelArg;
};
api.createUser = function(req, res) {
model.addUser(req.body.username, req.body.password);
console.log(res);
res.status(201);
};
You mocked a res object with this code:
let res = {end: function(){}};
that does not have a .status() method and then passed that to your api.createUser() function which expects to call res.status() (a method that is not on your mocked object). A mocked object will need to have every method on it that your code calls.
In addition, you are also testing the property res.statusCode with this:
expect(res.statusCode).to.equal(201);
which also does not exist on your mocked object. While res.status() is a built-in capability in Express, res.statusCode is not a documented property (though it does appear to exist).
You could add to your mocked res object like this:
let res = {
end: function(){}
status: function(s) {this.statusCode = s; return this;}
};
To get it to pass those two tests.
Related
I have the below js for unit testing an error handler:
import assert from 'assert';
import deepClone from 'lodash.clonedeep';
import deepEqual from 'lodash.isequal';
import { spy } from 'sinon';
import errorHandler from './index';
function getValidError(constructor = SyntaxError) {
let error = new constructor();
error.status = 400;
error.body = {};
error.type = 'entity.parse.failed';
return error;
}
describe('errorHandler', function() {
let err;
let req;
let res;
let next;
let clonedRes;
describe('When the error is not an instance of SyntaxError', function() {
err = getValidError(Error);
req = {};
res = {};
next = spy();
clonedRes = deepClone(res);
errorHandler(err, req, res, next);
it('should not modify res', function() {
assert(deepEqual(res, clonedRes));
});
it('should call next()', function() {
assert(next.calledOnce);
});
});
...(#other test cases all similar to the first)
describe('When the error is a SyntaxError, with a 400 status, has a `body` property set, and has type `entity.parse.failed`', function() {
err = getValidError();
req = {};
let res = {
status: spy(),
set: spy(),
json: spy()
};
let next = spy();
errorHandler(err, req, res, next);
it('should set res with a 400 status code', function() {
assert(res.status.calledOnce);
assert(res.status.calledWithExactly(400));
});
it('should set res with an application/json content-type header', function() {
assert(res.set.calledOnce);
assert(res.set.calledWithExactly('Content-Type', 'application/json'));
});
it('should set res.json with error code', function() {
assert(res.json.calledOnce);
assert(res.json.calledWithExactly({ message: 'Payload should be in JSON format' }));
});
});
});
Notice that I have let in front of res, next and clonedRes in the describe block for 'When the error is a SyntaxError...'.
Without the let in front of these, I get failures in my tests. I do not understand why I need to add the let for these again, but not for the err and req in that same block. Could anyone help me out with an explanation?
In strict mode (and in decently linted code in general), a variable must be declared before it's assigned to. Also, const and let variables must be declared once in a block, and no more. Re-declaring the err (or any other variable) which has already been declared will throw an error, which is why you should see let <varname> only once in your describe('errorHandler' function:
const describe = cb => cb();
let something;
describe(() => {
something = 'foo';
});
let something;
describe(() => {
something = 'bar';
});
Further describes inside of describe('errorHandler' already have scoped access to err.
Without declaring a variable first at all, assigning to it in sloppy mode will result in it being assigned to the global object, which is almost always undesirable and can introduce bugs and errors. For example:
// Accidentally implicitly referencing window.status, which can only be a string:
status = false;
if (status) {
console.log('status is actually truthy!');
}
That said, it's often a good idea to keep variables scoped as narrowly as possible - only assign to an outside variable when you need the value in the outer scope. Consider declaring the variables only inside of the describes that assign to them, which has an additional bonus of allowing you to use const instead of let:
describe('When the error is not an instance of SyntaxError', function() {
const err = getValidError(Error);
const req = {};
const res = {};
const next = spy();
const clonedRes = deepClone(res);
errorHandler(err, req, res, next);
// etc
});
// etc
describe('When the error is a SyntaxError, with a 400 status, has a `body` property set, and has type `entity.parse.failed`', function() {
const err = getValidError();
const req = {};
const res = {
status: spy(),
set: spy(),
json: spy()
};
const next = spy();
// etc
I'm writing some tests for a simple node.js module which returns the result of another function, currently that module looks like this:
const doAPostRequest = require('./doAPostRequest.js').doAPostRequest
const normalizeResponse = require('./normalizeResponse.js').normalizeResponse
const errorObject = require('./responseObjects.js').errorObject
const successObject = require('./responseObjects.js').successObject
exports.handlePost = (postData) => {
return doAPostRequest(postData).then((res) => {
const normalizedResponse = normalizeResponse(res);
return successObject(normalizedResponse);
}).catch((error) => {
const { statusCode, message } = error;
return errorObject(statusCode, message);
});
}
doAPostRequest is another module that returns a promise. Now i want to test for the following cases:
if doAPostRequest is being called once with the params given to handlePost.
is normalizeResponse is being called once with the params that are returned from doAPostRequest.
is successObject is being called once with the params that are returned from normalizedResponse.
is errorObject is being called once with the params that are returned from doAPostRequest (if there is an error).
But i can't figure out how i can test if the functions are called within the promise and if doAPostRequest is being called in the first place.
Currently i have the following test suite for the module written with the help of the Chai, Sinon and Sinon-chai libraries.
const sinon = require('sinon');
const chai = require('chai');
const handlePost = require('./handlePost.js').handlePost
const params = { body: 'title': 'foo' }
chai.use(require('sinon-chai'));
it('Calls doAPostRequest', () => {
doAPostRequest = sinon.stub().resolves("Item inserted")
handlePost(params)
expect(doAPostRequest).to.have.been.calledOnce;
expect(doAPostRequest).to.have.been.calledWith(params);
});
it('Calls normalizeResponse', () => {
normalizeResponse = sinon.stub().resolves("result")
return handlePost(params).then((res) => {
expect(normalizeResponse).to.have.been.calledOnce;
expect(normalizeResponse).to.have.been.calledWith("Item inserted");
});
});
However the tests fail with the following error response:
AssertionError: expected stub to have been called exactly once, but it was called 0 times
Maybe i'm overlooking something and is this not the right way to test this piece of code? I found this topic and the problem is close to identical, however for me non of the functions are getting called apparently. This leads to me thinking that i must overlook something. Any help and/or suggestions will be appreciated.
I have controller :
function(req, res) {
// Use the Domain model to find all domain
CIO.find(function(err, CIOs) {
if (err) {
response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
} else {
var metrics = {
"count": CIOs.length
};
// .then means it will wait for it to finish, then let you have the result
var promises = [];
for (i in CIOs) {
promises.push(Analysis.structureMetrics(CIOs[i].toObject()))
}
var output = []
var errors = []
Q.allSettled(promises)
.then(function(results) {
for (i in results) {
if (results[i].state === "rejected") {
console.log(results[i])
errors.push(results[i].reason.errors)
output.push(results[i].reason)
} else {
output.push(results[i].value)
}
}
}).then(function() {
response = responseFormat.create(true, "List of all CIOs", output, metrics, errors);
res.status(200).json(response);
})
}
});
};
and cio.test file :
describe('/cio', function() {
describe('GET', function() {
//this.timeout(30000);
before(function() {
});
it('should return response', function(done) {
var response = http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
var request = http_mocks.createRequest({
method: 'GET',
url: '/cio',
})
//var data = JSON.parse( response._getData() );
response.on('end', function() {
response.statusCode.should.be.equal(400);
done();
})
cioCtrl.getCIOs(request, response);
});
});
});
getting Error
Error: timeout of 10000ms exceeded. Ensure the done() callback is being called in this test
1>I have already tried increasing the time, but It doesn't work.
2> What I found is response.('end', function(){}) is not getting called, but not sure why
Any help would be appreciated.
Thanks!
Very good approach for unit testing is using the dependency injection.
For this, your controller file should be something like this:
module.exports = class MyController {
constructor(CIO) {
this._CIO = CIO;
this.handler = this.handler.bind(this);
}
handler(req, res) {
// your code here using this._CIO
}
};
Than in your main file, you create instance of controller:
const MyController = require('./controllers/my-controller');
// require your CIO, or create instance...
const CIO = require('./CIO');
const myController = new MyController(CIO);
You simply then pass instance of controller, or it's handler function to the place where it will be used.
Using this approach allows you to test well.
Assume your 'it' will look something like this:
it('should work', function(done) {
const fakeCIO = {
find: function() {
done();
}
};
const myController = new MyController(fakeCIO);
myController.handler();
});
Basic differences between testing techniques:
unit test - you test one single unit, how it calls functions, makes assignments, returns values
integration test - you add database to your previous test and check how it is stored/deleted/updated
end-to-end test - you add API endpoint to previous integration test and check how whole your flow works
Update:
Using async/await approach you will be able to test more things using your controller. Assume modifying it in something like this:
async function(req, res) {
try {
const CIOs = await CIO.find();
const metrics = {
"count": CIOs.length
};
const promises = CIOs.map(el => Analysis.structureMetrics(el.toObject());
for(const promise of promises) {
const result = await promise();
// do whatever you need with results
}
} catch(err) {
const response = responseFormat.create(false, "Error getting CIOs", err, {});
res.status(400).json(response);
}
Using such approach, during unit testing you can also test that your controller calls methods:
responseFormat.create
Analysis.structureMetrics
res.status
res.json
test catch branch to be executed
All this is done with fake objects.
And sure using OOP is not mandatory, it's just a matter of habits, you can do the same using functional style, with closures, for example.
I have a promise function which performs an authentication based on the clients cookie
const getInitialState = (id_token) => {
let initialState;
return new Promise((resolve,reject) => {
if(id_token == null){
initialState = {userDetails:{username: 'Anonymous',isAuthenticated: false}}
resolve(initialState)
}else{
var decoded = jwt.verify(JSON.parse(id_token),'rush2112')
db.one('SELECT * FROM account WHERE account_id = $1',decoded.account_id)
.then(function(result){
console.log('result is : ',result)
initialState = {userDetails:{username:result.username,isAuthenticated:true}}
resolve(initialState)
})
.catch(function(err){
console.log('There was something wrong with the token',e)
reject('There was an error parsing the token')
})
}
})
}
getInitialState is a promise object which calls a database function(another promise object) if the cookie is valid.
I want to stub the db call here to resolve to a username. But its not working no matter what i try
i tried two libraries sinonStubPromise and sinon-as-promised. But both seem to result in a timeout error which tells me that the db function isnt getting resolved
I believe i'm not stubbing the db function properly
these are the various ways i've tried
stub2 = sinon.stub(db,'one')
stub2.returnsPromise().resolves({username:'Kannaj'})
or
sinon.stub(db,'one').returns({username:'Kannaj'})
or
sinon.stub(db,'one')
.withArgs('SELECT * FROM account WHERE account_id = $1',1)
.returns({username:'Kannnaj'})
or
let db = sinon.stub(db).withArgs('SELECT * FROM account WHERE account_id = $1',1).returns({username:'Kannnaj'})
all lead to a timeout error from mocha
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
this is my entire test function
it('should return a valid user if id_token is valid',function(){
id_token = '{"account_id":1}'
console.log('stub1: ',stub1(), typeof(stub1))
console.log('stub2 : ',stub2,typeof(stub2))
// my attempts here
return expect(getInitialState(id_token)).to.eventually.be.true
})
For some reason , i believe mocha/sinon is loosing the pg-promise context as soon as it calls db.any . not sure why.
I can't speak to sinon-as-promised or sinonStubPromise, but you don't need them to accomplish something like this.
const sinon = require('sinon');
const chai = require('chai');
chai.use(require('chai-as-promised'));
const expect = chai.expect;
// real object
const db = {
one: function () {
// dummy function
}
};
// real function under test
function foo () {
return db.one('SELECT * FROM account WHERE account_id = $1');
}
describe('foo', function () {
beforeEach(function () {
sinon.stub(db, 'one')
.withArgs('SELECT * FROM account WHERE account_id = $1')
.returns(Promise.resolve({username: 'Kannaj'}));
});
it('should not timeout', function () {
return expect(foo())
.to
.eventually
.eql({username: 'Kannaj'});
});
afterEach(function () {
db.one.restore();
});
});
I m testing an application in which I need to test some callbacks behaviour.
I've isolated the callback and tried to make something testable.
I m having these two functions :
const callbackRender = (httpResponse, response) => {
if (httpResponse.content.content) response.send(httpResponse.content.content)
else response.render(httpResponse.content.page)
}
const callback = (injector, route) => {
return (request, response) => {
const ctrl = injector.get(route.controller)
const httpResponse = ctrl[route.controllerMethod](new HttpRequest())
callbackRender(httpResponse, response)
}
}
The test that I have failing is the following :
it('should call the callback render method when httpResponse is not a promise', () => {
const mock = sinon.mock(injector)
const ctrl = new UserControllerMock()
const routes = routeParser.parseRoutes()
mock.expects('get').returns(ctrl)
const spy = chai.spy.on(callbackRender)
callback(injector, routes[1])(request, response)
const controllerResult = ctrl.getAll(new HttpRequest())
expect(spy).to.have.been.called.with(controllerResult, response)
mock.verify()
mock.restore()
})
The problem happens on the spy that has not been called.
But when I log the expected result, and if I log what I pass as parameter in the callbackRender, I have exactly the same JSON object.
Here's the chai :
AssertionError: expected { Spy } to have been called with [ Array(2)
]