/ NodeJS

NodeJS — A beginners guide to a lot of stuff. Part 4

Go back to part 3

We’ve come a long way already. We built our interface, a mock implementation and a test suite that has a reusable test block with an injectable implementation, but, so far, we have nothing more that tests on a mock object. How about taking that to the next level?

Let’s do something worthwhile with all this code. As mentioned way back in the first article, you had to switch from MongoDB to a relational database, which caused all this mess in the first place. So let’s try and see how we can create a MySQL implementation of our Category Model next.

Before we dive into the model, you’ll need to add MySQL support to your node app. I use a script I grabbed out of some dark corner of the web that helps me setup my app. I have a GIST of it I always keep handy

This basically gives me some helper methods to connect to and query the MySQL database. I’ll use them later on this article.
A quick note on the config require — config will load in JSON files in your config directory according to you NODE_ENV parameter. For example, if your NODE_ENV is ‘test’ — it will fetch the ./config/test.json file and convert it into an object accessible via the config object. You will see some of that below too.

I might write a quick article about the config package some time soon.

Like we did with our mock implementation, we will build a constructor to inject all that we need into our object, and setup the methods. I will focus only on the getAll method to keep things simple.

var ICategoryRepository = require('../interfaces/models/ICategoryRepository');
// CONSTRUCTOR
// We inject the db into it.
var CategoryMysql = function(db){
  this.db = db;
}
// Load the methods from the interface
CategoryMysql.prototype = Object.create(ICategoryRepository);
CategoryMysql.prototype.getAll = function(done){
  try{
    this.db.get().query('SELECT * FROM categories',[] ,(err, rows) =>{
      if (err) return done(err);
        return done(null, rows);
    })
  } catch(err){
    return done(err, null);
  }
}

If you compare this to our mock implementation, you will see that they share a lot in common; they follow the same structure, they both have a constructor with an injection, they both import the interface methods, they both overload the getAll method, and both getAll methods return a callback with two parameters.

Let’s assume we have implemented the rest of the methods accordingly. Following suit. What have you done? You have created an object that behaves, as far as external viewers can see, exactly like our mock object.
In theory, if now we go to our tests, we could substitute our mock object for this brand new mysql object and everything should remain the same.
So let’s do that!

Remember all that code at the top of the test.js file?

process.env.NODE_ENV = 'test';
const db = require('../src/mysql');
const config = require('config');
try{
  db.connect((err) =>{
    if (err){
      throw Error('Could not connect to database');
    }
  })
}catch(err){
  console.log(err);
  process.exit(1);
}

Well this part handles creating our database helper (requiring the one from the GIST) and connecting to our database and giving us a connection object, in this case, the variable db.
This is where we injected our mock object into our tests:

describe('Test the mocked Category', function(){
  runCategoryTest(MockCat);
})

Where MockCat was a new MockCategory, receiving in sample data, remember?

Better than replacing it with our new MySQL object, why don’t we add a new test suit to try our our MySQL object?

// require our mysql object
var MysqlCat = require('../../src/models/CategoryMysql')
// create a new instance and inject our DB object into it
var myCat = new MysqlCat(db);
// finally call our tests with the instance injected into it
describe('Test the mySQL Category Implementation', function(){
  runCategoryTest(myCat);
})

In under 10 lines of code (including the comments), you have just added the whole suit of tests on category to be run against our MySQL implementation.

Guess what? You could also do implementations for Redis, MongoDB, AmazonS3, PostgreSQL, SQLLite, flat file, or whatever else you can think of… it’s that simple.

We learned that by testing against the methods of an interface, rather than to a specific implementation, we can easily abstract the implementation; Category does not care if it is on MySQL or MyLittlePonyPoweredUltaSql. It cares about the methods and returns. So when your app calls a Category.getAll(callback), you don’t need to know where the data is, just that this data will be returned properly and consistently.

You also have the benefit of, almost instantly, getting full test coverage for new implementations. Since you are testing against methods, it is unlikely that you will have paths that have not been diagnosed before. And any unlikely error that may creep up, will cause you to have a better refactoring.

Final Thoughts

You might wonder why I started off with a mock object and then tests. It’ s a legitimate thought. I’ve opted to develop all my models as mock implementations first. I do this development in a some whatTDD workflow. I create a small group of tests with desired outcomes for a specific method. Then I implement that method until my tests are clear. I will do that for each method of my object, for each object. Once I have the whole system working with mock objects, I can start to implement them in MySQL or whatever implementation I choose to go with.
It sounds like a bit of overkill, but, by doing the heavy testing on a mock object, I can refactor quicker that if I had to interact with a database.

Hope you enjoyed this series of articles.

Part 1
Part 2
Part 3

Gregory Brown

Gregory Brown

Baker, cake designer, cook and just by chance, a full featured developer that is passionate about technology.

Read More