Mocha Unit Testing of Controller resolving promise coming from services - javascript

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.

Related

How do I test a class's method that has arguments using sinon.js

Hi I am using sequelize ORM with Postgres database for this node.js express.js app. As for testing I am using mocha, chai and sinon.
I am trying to complete a test for a class's method. The class instant i call it userService and the method is findOneUser .. This method has got an argument id .. So in this moment I want to test for a throw error the test works if I dont put an argument. That means this test is obviously not complete.
Here is the class method I want to test
userService.js
module.exports = class UserService {
async findOneUser(id) {
try {
const user = await User.findOne({ where: { id: id } }); // if null is returned error is thrown
if (!user) {
throw createError(404, "User not found");
}
return user;
} catch (err) {
throw err
}
}
}
And here is my test code
userService.spec.js
describe.only("findOne() throws error", () => {
let userResult;
const error = customError(404, "User not found"); // customError is a function where I am throwing both status code and message
before("before hook last it block issue withArgs" , async () => {
// mockModels I have previously mocked all the models
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).resolves(null); // basically have this called and invoked from calling the method that it is inside of based on the argument fakeId
userResult = sinon.stub(userService, "findOneUser").throws(error); // 🤔this is the class instances method I wonder how to test it withArguments anything I try not working but SEE BELOW COMMENTS🤔
});
after(() => {
sinon.reset()
});
it("userService.findOneUser throws error works but without setting arguments 🤔", () => {
expect(userResult).to.throw(error);
});
/// this one below still not working
it("call User.findOne() with incorrect parameter,, STILL PROBLEM 🤯", () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
});
But for the method of my class findOneUser has an argument (id) how can I pass that argument into it where I am stubbing it?
Or even any ideas on how to fake call the class method?? I want both it blocks to work
EDIT
I forgot to mention I have stubbed the mockModels.User already and that was done before the describe block
const UserModel = {
findByPk: sinon.stub(),
findOne: sinon.stub(),
findAll: sinon.stub(),
create: sinon.stub(),
destroy: sinon.stub()
}
const mockModels = makeMockModels( { UserModel } );
// delete I am only renaming UserModel to User to type things quicker and easier
delete Object.assign(mockModels, {['User']: mockModels['UserModel'] })['UserModel']
const UserService = proxyquire(servicePath, {
"../models": mockModels
});
const userService = new UserService();
const fakeUser = { update: sinon.stub() }
SOLUTION
I think this might be the solution to my problem strange but it works the tests is working with this
describe.only("findOne() throws error", async () => {
const errors = customError(404, "User not found"); // correct throw
const errors1 = customError(404, "User not foundss"); // on purpose to see if the test fails if should.throw(errors1) is placed instead of should.throw(errors)
after(() => {
sinon.reset()
});
// made it work
it("call User.findOne() with incorrect parameter, and throws an error, works some how! 🤯", async () => {
userResult = sinon.spy(userService.findOneUser);
try {
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
await userResult(fakeId);
} catch(e) {
// pass without having catch the test fails 😵‍💫
} finally {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
}
});
it("throws error user does not exist,,, WORKS", () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw
mockModels.User.findOne.withArgs(fakeId).should.throw(errors); // specially this part without having the catch test fails. but now test works even tested with errors1 variable
expect(userResult).to.throw;
});
});
MORE CLEANER SOLUTION BELOW
I like this solution more as I call the method inside within the describe block, then I do the test of the two it blocks.
The problem was I should not have stubbed the class method but should have called it directly, or used sinon.spy on the method and call it through the spy. As for checking that the errors are as expected then running this line of expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors); was the solution I needed.
describe.only('findOne() user does not exists, most cleanest throw solution', () => {
after(() => {
sinon.reset()
});
const errors = customError(404, "User not found");
mockModels.User.findOne.withArgs({ where: { id: fakeId } }).threw(errors);
userResult = sinon.spy(userService, "findOneUser"); // either invoke the through sinon.spy or invoke the method directly doesnt really matter
userResult(fakeId);
// userResult = userService.findOneUser(fakeId); // invoke the method directly or invoke through sinon.spy from above
it('call User.findOne() with invalid parameter is called', () => {
expect(mockModels.User.findOne).to.have.been.calledWith({ where: { id: fakeId } });
})
it('test to throw the error', () => {
expect(mockModels.User.findOne.withArgs(fakeId).throws(errors)).to.throw(errors);
expect(userResult).to.throw;
})
});

Sinon: Stubbing static method of class does not work as expected

I want to mock the following class, that is used as dependency for another one:
module.exports = class ResponseHandler {
static response(res, status_, data_, codes_) {
console.log('Im here. Unwillingly');
// doing stuff here...
};
The ResponseHandler is imported by the ProfileController and used there:
const response = require('../functions/tools/response.js').response;
module.exports = class ProfileController {
static async activateAccountByVerificationCode(req, res) {
try{
// doing stuff here
return response(res, status.ERROR, null, errorCodes);
}
}
Now I'm writing Unit Tests for the ProfileController and there I'm testing if activateAccountByVerificationCode calls response with the given arguments
describe('ProfileController', () => {
let responseStub;
beforeEach(function() {
responseStub = sinon.stub(ResponseHandler, 'response').callsFake(() => null);
});
But despite the fact that response is mocked, the ProfileController still calls the real implementation of response (See console output: 'Im here. Unwillingly')
it('should respond accordingly if real verification code does not fit with the one passed by the user', async function () {
// here you can still see that real implementation is still called
// because of console output 'I'm here unwillingly'
await controller.activateAccountByVerificationCode(req, res);
console.log(responseStub.called); // -> false
expect(responseStub.calledWith(res, status.ERROR, null, [codes.INVALID_VERIFICATION_CODE])).to.eql(true); // -> negative
});
You need to mock you controllers dependecies with a library like proxyquire first and then use this mocked instance in you test. Otherwise you will still use the original (unstubbed) implementation.
const proxyquire = require('proxyquire');
describe('ProfileController', () => {
let responseStub;
let Controller;
beforeEach(function() {
responseStub = sinon.stub(ResponseHandler, 'response').callsFake(() => null);
Controller = proxyquire('./ProfileController', {'../functions/tools/response':responseStub})
});
it('should respond accordingly if real verification code does not fit with the one passed by the user', async function () {
// here you can still see that real implementation is still called
// because of console output 'I'm here unwillingly'
await Controller.activateAccountByVerificationCode(req, res);
console.log(responseStub.called); // -> false
expect(responseStub.calledWith(res, status.ERROR, null, [codes.INVALID_VERIFICATION_CODE])).to.eql(true); // -> negative
});
Controller then uses your stubbed version of the function and can be inspected.

Jest testing mongoose model instantiation

I'm trying to test a REST API built with express and mongoose, I'm using jest and supertest for the http calls; also I'm relatively new to testing with javascript.
When testing a creation url I wan't to make sure the instantiation is called using just the req.body object but I'm not sure how to do it, after reading a lot about differences between mock objects and stubs and some of the Jest documentation my last try looks like this:
test('Should instantiate the model using req.body', done => {
const postMock = jest.fn();
const testPost = {
name: 'Test post',
content: 'Hello'
};
postMock.bind(Post); // <- Post is my model
// I mock the save function so it doesn't use the db at all
Post.prototype.save = jest.fn(cb => cb(null, testPost));
// Supertest call
request(app).post('/posts/')
.send(testPost)
.then(() => {
expect(postMock.mock.calls[0][0]).toEqual(testPost);
done();
})
.catch(err => {throw err});
});
Also I would like to know how to manually fail the test on the promise rejection, so it doesn't throws the Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
As it stands, you're performing more of a integration test rather than isolating the route handler function itself and testing just that.
First I would break away the handler for /posts/ to its own file (assuming you haven't done this already):
controllers/post-controller.js
const Post = require('./path/to/models/post')
exports.store = async (req, res) => {
const post = await new Post(req.body).save()
res.json({ data: post }
}
Next simply use the handler wherever you defined your routes:
const express = require('express')
const app = express()
const postController = require('./path/to/controllers/post-controller')
app.post('/posts', postController.store)
With this abstraction we can now isolate our postController.store and test that it works with req.body. Now since we need to mock mongoose to avoid hitting an actual database, you can create a mocked Post like so (using the code you already have):
path/to/models/__mocks__/post.js
const post = require('../post')
const mockedPost = jest.fn()
mockedPost.bind(Post)
const testPost = {
name: 'Test post',
content: 'Hello'
}
Post.prototype.save = jest.fn(cb => {
if (typeof cb === 'function') {
if (process.env.FORCE_FAIL === 'true') {
process.nextTick(cb(new Error(), null))
} else {
process.nextTick(cb(null, testPost))
}
} else {
return new Promise((resolve, reject) => {
if (process.env.FORCE_FAIL === 'true') {
reject(new Error())
} else {
resolve(testPost)
}
})
}
})
module.exports = mockedPost
Notice the check for process.env.FORCE_FAIL if for whatever reason you wanted to fail it.
Now we're ready to test that using the req.body works:
post-controller.test.js
// Loads anything contained in `models/__mocks__` folder
jest.mock('../location/to/models')
const postController = require('../location/to/controllers/post-controller')
describe('controllers.Post', () => {
/**
* Mocked Express Request object.
*/
let req
/**
* Mocked Express Response object.
*/
let res
beforeEach(() => {
req = {
body: {}
}
res = {
data: null,
json(payload) {
this.data = JSON.stringify(payload)
}
}
})
describe('.store()', () => {
test('should create a new post', async () => {
req.body = { ... }
await postController(req, res)
expect(res.data).toBeDefined()
...
})
test('fails creating a post', () => {
process.env.FORCE_FAIL = true
req.body = { ... }
try {
await postController.store(req, res)
} catch (error) {
expect(res.data).not.toBeDefined()
...
}
})
})
})
This code is untested, but I hope it helps in your testing.

testing promises causing undefined values

I am getting this error when I am testing my code:
1) Sourcerer Testing: getStatusCode :
Error: Expected undefined to equal 200
I'm not sure why I am getting undefined in my tests but when I run the code I get 200. It might be from not handling promises properly
Test code:
import expect from 'expect';
import rp from 'request-promise';
import Sourcerer from './sourcerer';
describe("Sourcerer Testing: ", () => {
let sourcerer = new Sourcerer(null);
const testCases = {
"https://www.google.com": 200,
// "www.google.com":
};
describe("getStatusCode", () => {
it("", () => {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
expect(sourcerer.getStatusCode()).toEqual(testCases[testCase]);
}
});
});
});
code:
import rp from 'request-promise';
export default class Sourcerer {
constructor(url) {
this.options = {
method: 'GET',
url,
resolveWithFullResponse: true
};
this.payload = {};
}
setSourcererUrl(url) {
this.url = url;
}
getSourcererUrl() {
return this.url;
}
analyzeSourcePage() {
rp(this.options).then((res) => {
console.log(res);
}).catch((err) => {
console.log("ERROR");
throw(err);
});
}
getStatusCode() {
rp(this.options).then((res) => {
console.log(res.statusCode);
return res.statusCode;
}).catch((err) => {
console.log("STATUS CODE ERROR");
return 0;
});
}
}
getStatusCode doesn't return anything. And it should return a promise:
getStatusCode() {
return rp(this.options)...
}
The spec will fail in this case, because it expects promise object to equal 200.
It is even more complicated because the spec is async and there are several promises that should be waited before the spec will be completed. It should be something like
it("", () => {
let promises = [];
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
let statusCodePromise = sourcerer.getStatusCode()
.then((statusCode) => {
expect(sourcerer.url).toEqual(testCase);
expect(statusCode).toEqual(testCases[testCase]);
})
.catch((err) => {
throw err;
});
promises.push(statusCodePromise);
}
return promises;
});
co offers an awesome alternative to Promise.all for flow control:
it("", co.wrap(function* () {
for (let testCase in testCases) {
sourcerer.setSourcererUrl(testCase);
expect(sourcerer.url).toEqual(testCase);
let statusCode = yield sourcerer.getStatusCode();
expect(statusCode).toEqual(testCases[testCase]);
}
});
Disclaimer: I wouldn't run a for-loop in a single it(), since I want to know which iteration failed. granted that there are ways to achieve that, but that is another story. Also, this very much depends on you test runner, but here is some rules of thumb I find useful.
But for what you have asked, the test should not evaluate until the promise is resolved. sometimes (e.g. in mocha), that means returning the promise from the it() internal function. sometimes, it means getting a done function and calling it when you are ready for the test to evaluate. If you provide more info on your test framework, I may be able to help (others certainly would be)

how should i do asynchronous unit testing?

I am using pg-promise. i am learnner please excuse if it seems trivial to you. how can i wrtie unit test for. it errors out data is undefined. i have been making connection in js file and export that module.another js file use to query against database and fetch result set. code is working as expected having issue how can i write unit test with mocha and chai.
test1.js
var dbConn= pgp(connUrl);
module.exports = {
getconnect: function () {
return dbConn;
}
};
test2.js
module.exports = {
getData: function (req, res) {
db.getconnect().query(sqlStr, true)
.then(function (data) {
console.log("DATA:", data);
return data;
} } }
unittest.js
describe("Test Cases", function (done) {
it('retrieve response', function (done) {
var req = {};
var res = {};
test2.getData(req, res);
// how would i retrieve value of data from test2.js so i can test
done();
});
});
how would i retrieve "data" value from test2.js inthe unittest.js
Your getData must return the promise. Client code will be able to recognize the moment it's finished(resolved).
module.exports = {
getData: function (req, res) {
return db.getconnect().query(sqlStr, true)
.then(function (data) {
console.log("DATA:", data);
return data;
} } }
test:
describe("Test Cases", function () {
it('retrieve response', function (done) {
var req = {};
var res = {};
test2.getData(req, res).then(function(data){
// test of data returned
done(); // finish test
}).catch(done);// report about error happened
});
});
If you don't need to any process of data in your module, you can to remove whole .then section without any functionality changes.
But if you want to preprocess data - don't forget to return it from each chained .then.
If your test library requires stubs for async stuff, you can use async/await feature to deal with it.
it('retrieve response', async function(){
try {
var data = await test2.getData(req, res);
// test data here
} catch (e) {
// trigger test failed here
}
});
Or stub it, something like this:
var dbStub = sinon.stub(db, 'getConnect');
dbStub.yields(null, {query: function(){/*...*/}});
If you have a function returning promise then you can use await in your tests:
describe("Test Cases", function (done) {
it('retrieve response', async function (done) {
try {
var data = await test2.getData();
// check data constraints ...
...
} catch(err) {
...
}
done(); // finish test
});
});

Categories

Resources