Testing nodejs models

Testing the model layer introduces a set of challenges relating to reading and writing to a database. This article clears some of the challenges to avoid side effects and makes it possible to test the model layer in isolation.

One of the components that lay the groundwork for data-driven layered applications is the model layer. However, resources about testing, in general, do not address advanced concepts such as how to isolate components for better composability and healthy test coverage.

In this article we will talk about:

Even though this blog post was designed to offer complementary materials to those who bought my Testing nodejs Applications book, the content can help any software developer to tuneup working environment. You use this link to buy the book. Testing nodejs Applications Book Cover

Show me the code

//in lib/models/user.js
var UserSchema = new mongoose.Schema({name: String});
UserScheme.statics.findByName(function(name, next){
    //gives: access to Compiled Model
    return this.where({'name': name}).exec(next);
});

UserSchema.methods.addEmail(function(email, next){
    //works: with retires un-compiled model
    return this.model('User').find({ type: this.type }, cb);
});

//exporting the model 
module.exports = mongoose.model('User', UserSchema);        

//anywhere else in UserModel is used 
User.findById(id, function(error, user){
    if(error) return next(error);
    return res.status(200).json(user);
});

new User(options).save(function(error, user){
  if(error) return next(error);
  return next(null, user); 
});

Example: mongoose Model definition example in model/user.js

What can possibly go wrong?

When trying to figure out how to approach mocking chained model read/write functions, the following points may be a challenge:

Choosing tools

If you haven't already, reading “How to choose the right tools” blog post gives insights on a framework we used to choose the tools we suggest in this blog.

Following our own “Choosing the right tools” framework, we adopted the following tools, when testing mongoose models:

Workflow

It is possible to generate reports as tests progress.

latest versions of istanbul uses nyc code name.

# In package.json at "test" - add next line
> "istanbul test mocha -- --color --reporter mocha-lcov-reporter specs"

# Then run the tests using 
$ npm test --coverage 

Example:

Show me the tests

If you haven't already, read the “How to write test cases developers will love”

This blog post approaches testing of fairly large nodejs application from a real-world perspective and with refactoring in mind.

sinon stubs to simulate a response from Mongo::UserSchema::save() function, its equivalents.


describe('User', () => {
    beforeEach(() => {
        ModelSaveStub = sinon.stub(User.prototype, 'save', cb);
        ModelFindStub = sinon.stub(User, 'find', cb);
        ModelFindByIdStub = sinon.stub(User, 'findById', cb);
    });

    afterEach(() => { 
        //... 
    });
    
    it('should findByName', (done) => {
        User.findByName('Jane Doe', (error, users) => {
            expect(users[0].name).toBe('Jane Doe');
            done();
        });
    });
    
    it('should addEmail', (done) => {
        User.addEmail('jane.doe@jd.com', (error, email) => {
            expect(email).toBe('Jane Doe');
            done();
        });
    });
});

To learn more about mocking database functions, please read this article.

Conclusion

Automated testing of any JavaScript project is quite intimidating for newbies and veterans alike. In this article, we reviewed how testing tends to be more of art, than science.

We also stressed the fact that, like in any art, practice makes perfect ~ testing models is challenging especially when a read/write to an actual database is involved. There are additional complimentary materials in the “Testing nodejs applications” book.

References

#snippets #code #annotations #question #discuss