What is a Humble Object and how does it help make your code testable?

Tags:

Some of the most common testing-related questions I’m asked relate to testing real-world apps. There’s always a difficult part that’s hard to test. Most often, it’s been the database. Sometimes I’ve also been asked about HTTP-based APIs like RESTful APIs and such.

Let’s imagine a typical situation where you have some code that uses a database. You’ve got some code which queries the database, and then does something with the result.

At first, there’s doesn’t seem to be anything wrong with this approach.

The problem shows up when you try to test the code. You’ve got to deal with all the database related logic, when all you want to do is make sure the function is working correctly!

We’ve talked a lot about Sinon.js on several occasions. Sinon is a great tool, because it helps you solve problems like this… but knowing how to use a tool isn’t useful at all if your code makes it hard to use.

That’s why in this article, I’m going to tell you a simple and efficient way of making almost any kind of application testable. And best of all, it doesn’t usually require huge changes to your code either!

Our design problem

Let’s think about the problem for a moment.

You’ve got a bunch of code, which does some data processing. But in order to do that, it first needs to get the data from a database. OK, a fairly normal situation.

Let’s say we have a function that builds a tree object for a content management system’s pages…

function buildPageTree(siteId, callback) {
  var statement = mysql.query('SELECT * FROM pages WHERE site_id = ?');
  statement.param('site_id', siteId);
  statement.run(function(err, results) {
    var pagesById = sortPagesById(results);
    results.forEach(function(r) {
      pagesById[r.parentId].children.push(r);
    });
 
    callback(findPageWithNoParent(pagesById));
  });
}

The logic is not hugely complex – the function fetches some data from MySQL, and then builds a tree of pages. Each page can have a number of child pages. The page with no parent is returned, since that is the root page, with other pages always being its children via some relationship.

How could we test this function? At the first glance, it looks like we’d need to set up a test database. No way. That’s too much work… so let’s just not test this.

But it’s important to test this function! Although the logic is not that complicated, building a tree from a list can have subtle bugs and edge-cases.

Is there an alternative? Sure, you can just brute-force it, and use Sinon and mock and stub your way around everything… but you end up with super complex tests, and they keep breaking every time you change your code. Imagine having to set up stubs for the mysql object. Argh, what a pain in the ass.

var query = sinon.stub(mysql, 'query');
var statement = {
  param: sinon.stub(),
  run: sinon.stub().yields(undefined, expectedResults)
};
query.withArgs('SELECT * FROM pages WHERE site_id = ?').returns(statement);

No thanks. It’s doable, but that’s going to be so annoying to deal with in tests. And this is just a single function example – in a real app, you probably have several different functions which all use the database!

It’s not a problem with our tools (Sinon will do it, it’s just not going to be easy), but rather, a problem with the design of our code.

You can replace the database in this situation with anything else. It could be a RESTful API, it could be a network connection… or it could even just be some other object in your code which just happens to be a bit complicated to deal with.

Here’s an illustration I made in Paint that describes this situation:

How to fix our design?

Almost all unit testing related problems stem from the same issue: Your function is doing too much work.

This is why TDD is great. It forces you to think about the structure and testability up front – but how to best do that is a topic for another time (sign up for my newsletter and I’ll make sure you’ll know when)

When you work using TDD, you naturally end up splitting your code into more modules.

The solution to most testing problems: Split your code, so individual functions or modules contain less logic in them.

But how do you know which parts of code you need to split out?

A good way is to think of what your code does. Often, you can split your code into two broad categories:

  1. Code that does something with data, such as calculations or changes to values
  2. and code which deals with moving data in and out, such as loading things from a database or file, or calling other functions to get values

More often than not, if a function does both of these categories, you’ve probably identified a problem. Split each out into its own module or function, and you’ll find it easier to test.

The Humble Object

So what is a Humble Object and how does it relate to this? Let’s look at the database example again.

In order to make our database using code testable, we need to split the code into two. One module which handles only the database related logic. Another module which handles only the other business logic.

var database = {
  loadPagesForSite: function(siteId, callback) {
    var statement = mysql.query('SELECT * FROM pages WHERE site_id = ?');
    statement.param('site_id', siteId);
    statement.run(callback);
  },
 
  otherDbFunction...,
};
 
function buildPageTree(siteId, callback) {
  database.loadPagesForSite(siteId, function(err, results) {
    var pagesById = sortPagesById(results);
    results.forEach(function(r) {
      pagesById[r.parentId].children.push(r);
    });
 
    return findPageWithNoParent(pagesById);
  });
}

Just like the above two categories, we end up with two modules:

  1. A database module, which handles the moving of data in and out (from the database)
  2. and a business logic module, which does the actual changes on the data

The result

The database module is a so-called Humble Object (the small pile) – it contains all the hard to test parts.

As a result, testing our business logic module is now very easy! We can take Sinon.js and stub or mock the Humble Object’s functions, without having to deal with the complicated database-related functionality.

var loadPages = sinon.stub(database, 'loadPagesForSite');
loadPages.yields(undefined, expectedResults);

Easiest. Stub. Ever.

Conclusion

If we have a problem testing a piece of code, it’s very likely it’s simply doing too much work. If we split it into two or more components, we’ll usually find them much easier to test. As an added benefit, this often helps make your code more modular, and thus easier to reuse and easier to reason about. How’s that for a triple whammy?

Almost all unit testing related problems can be solved by refactoring your code in this fashion, which makes using Sinon.js to stub other components very easy.

To find out more about the Humble Object pattern, check out Humble Object in xUnit Patterns.

There’s one more refactoring that would make sense to do for this code. The buildPageTree function could be changed so, that instead of directly querying a database, it takes pages as its parameter. This would simplify the function even further, making it even easier to test. Plus, the function would become very easy to reuse, should we need the logic somewhere else. But I’ll leave that as an exercise to you.