5
\$\begingroup\$

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();
    })
});
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You have a couple HTTP return codes here in there in your code, but don't explain what each return code means.

I recommend creating an object of the error codes with descriptive names so you can use those in place of just the plain code; it'll aid your readability greatly.

Here is what I came up with:

var HTTP_CODES = {
    CLIENT = {
        NOT_FOUND: 404,
        BAD_REQUEST: 400
    },
    SUCCESS = {
        CREATE:201
    }
}

Then, you can easily access these codes like this, for example:

res.status(HTTP_CODES.CLIENT.NOT_FOUND);

Notice how I used all capitals for the naming? Generally across other languages, all capital letters is used for constant values.


From postPerson,

if (err) {
    res.status(400);
    res.json(err);
}
else { // <----------
    var r = {
        message: "New person created",
        person: p
    };
    res.json(r);
    res.status(201);
}

Typically, the else { will come on the same line as the close } of the preceding if statement.

You wrote it the correct way in getPerson so I don't know why you changed it.


    function MockPerson(person) {

    this.content = {};
    for (var k in person) {

The signature of MockPerson should not be indented.


I realized that prototype functions are only accessible in instances of Person and statics only from "class" Person

The line you entered after:

MockPerson.prototype.findOne = MockPerson.findOne = function(params, callback)

Looks perfectly fine to me. However, do you really need to have a method that can be accessed as static and from an instance?

You say in a comment:

//Mongoose supports both Person.findOne and var p = new Person(); p.findOne() so we need both prototype and static functions

"Mongoose supports...". I don't know much about Mongoose, but if it supports both, can't you choose one?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.