I'm writing unit-tests for my Node.js/Express application with REST endpoints which retrieve stuff via Mongoose from db. Since I'm testing only route functions I want to mock Mongoose by providing custom request and response functions etc. I also had to mock my Person mongoose schema module with mockery.
Here is my api-endpoint functions:
/* GET one person */
function getPerson(req, res, next) {
Person.findOne({_id: req.params.id}, function(err, person) {
if (!person) {
res.status(404);
res.json({
message: "ERROR: Person with id: " + req.params.id + " was not found from database."
})
} else {
res.json(person)
}
});
}
/* POST one person */
function postPerson(req, res, next) {
console.log(Person);
var p = new Person(req.body);
p.save(function(err) {
console.log(p);
console.log(Person);
if (err) {
res.status(400);
res.json(err);
}
else {
var r = {
message: "New person created",
person: p
};
res.json(r);
res.status(201);
}
});
}
The important part is that the Person schema can be called in two ways:
Person.save(function(){}) //static function
var p = new Person(req.body);
p.save(function(err){}) //instance function
Here is my mockery mock for Mongoose schema of Person:
function MockPerson(person) {
this.content = {};
for (var k in person) {
if (person.hasOwnProperty(k)) {
this.content[k] = person[k]; //add person's properties here to simulate Mongoose's Object
}
}
}
MockPerson.prototype.find = function(params, callback) {
console.log("mockfind");
return null
};
//Ugly. Note that in JS prototype functions are accessible only from instances and statics only from class.
//Mongoose supports both Person.findOne and var p = new Person(); p.findOne() so we need both prototype and static functions
MockPerson.prototype.findOne = MockPerson.findOne = function(params, callback) {
console.log("mockfindOne");
callback(null, null);
};
MockPerson.prototype.save = MockPerson.save = function(callback) {
console.log("mocksave");
callback();
};
I'm still quite new with JS so I got a bit confused until I realized that prototype functions are only accessible in instances of Person and statics only from "class" Person. So this led me to add lines:
MockPerson.prototype.findOne = MockPerson.findOne = function(params, callback)
Maybe it's just me but that looks a bit dubious. Any ideas how I could refactor either my endpoint code or mocks or is this okay in your opinion?
Finally here is my work in progress Mocha test for this thing:
describe('personApiEndpoints', function() {
before(function(){
// The before() callback gets run before all tests in the suite. Do one-time setup here.
mockery.enable({ useCleanCache: true });
console.log("MOCKKAA");
mockery.registerMock("../../models/person", MockPerson);
endpoints = require("../routes/api/persons");
});
beforeEach(function(){
// The beforeEach() callback gets run before each test in the suite.
});
it('return approriate error response when getPerson() fails', function(done) {
var mockRequest = createMockRequest();
var mockResponse = createMockResponse();
endpoints.getPerson(mockRequest, mockResponse);
expect(mockResponse.resStatus).to.equal(404);
expect(mockResponse.resJson).to.deep.equal({ message: 'ERROR: Person with id: 12345678910 was not found from database.' });
done();
});
it('return response when postPerson succeeds', function(done) {
var mockRequest = createMockRequest();
var mockResponse = createMockResponse();
endpoints.postPerson(mockRequest, mockResponse);
expect(mockResponse.resStatus).to.equal(201);
expect(mockResponse.resJson.message).to.equal("New person created");
expect(mockResponse.resJson.person.content).to.deep.equal(mockRequest.body);
done();
});
after(function() {
// after() is run after all your tests have completed. Do teardown here.
mockery.deregisterAll();
mockery.disable();
})
});