---
title: Testing Null Objects
teaser:
tags: web,testing
author: George Brocklehurst
published_on: 2013-09-10
---

A Null Object is a drop in replacement for one of the other objects in your
system that provides sensible defaults when the other object is unavailable. For
example, I recently wrote about [returning the path to a blank partial as a
sensible default for `to_partial_path`][1]. Null Objects remove `respond_to?`
and `nil?` checks, and make code cleaner and easier to understand. It almost
seems like there's no downside.  Almost.

## Too good to be true

As with everything else in software development, there is a trade-off involved
in introducing a Null Object: Whenever we change the public interface of the real
object, we have to make a corresponding change in the Null Object that shadows
it. If the interfaces diverge, then the Null Object ceases to be useful; instead
of hiding complexity, it hides the potential for unexpected `NoMethodError`
exceptions. I might even go so far as to say that Null Objects usually smell a
little bit like [Shotgun Surgery][2].

## Unit tests to the rescue

Our Null Objects are only doing their job when they have the same public
interface as another object, so we should treat this like any other expectation
we have about the public interface of one of our objects and ensure it with a
unit test.

To ensure this requirement I recently added a test that looked something like this:

    describe NullGraph do
      it 'exposes the same public interface as Graph' do
        expect(described_class).to match_the_interface_of Graph
      end
    end

When the public interface of `NullGraph` includes all of the methods from
`Graph`'s public interface then the test passes. When the interfaces differ, the
test fails with a helpful message telling me which methods are missing from
`NullGraph`. When I see this test fail, I can add another test with an
expectation of what the Null Object's implementation of the missing method
should return.

This is all made possible by a [custom RSpec matcher][3]:

    RSpec::Matchers.define :match_the_interface_of do
      match do
        missing_methods.empty?
      end

      failure_message_for_should do
        "expected #{actual.name} to respond to #{missing_methods.join(', ')}"
      end

      def missing_methods
        expected_methods - actual_methods - common_methods
      end

      def expected_methods
        expected.map(&:instance_methods).flatten
      end

      def actual_methods
        actual.instance_methods
      end

      def common_methods
        Object.instance_methods
      end
    end

## The big picture

I've found this technique and this matcher to be useful, and I hope you do too.
There's a more important principle at work here though: Everything is a
trade-off, even good (or [trendy][4]) solutions add complexity. We should always
be asking ourselves what form that complexity takes, so that we can understand
it and mitigate its effects on our ability to change the code in future.

When it comes to thinking about the trade-offs involved in your design
decisions, I can heartily recommend [POODR][5] by [Sandi Metz][6], and
thoughtbot's own [Ruby Science][7]. Both books have helped me see these issues
more clearly.

## What's next

If you found this useful, you might also enjoy:

* [Design Patterns in the Wild: Null Object][wild]
* [Rails Refactoring Example: Introduce Null Object][refactoring]
* [Test-Driven Development with RSpec and Capybara][tdd]

[wild]: https://thoughtbot.com/blog/design-patterns-in-the-wild-null-object
[refactoring]: https://thoughtbot.com/blog/rails-refactoring-example-introduce-null-object
[tdd]: https://thoughtbot.com/upcase/test-driven-rails

[1]: https://thoughtbot.com/blog/post/58313551647/renderable-null-objects "Renderable Null Objects"
[2]: http://en.wikipedia.org/wiki/Shotgun_surgery
[3]: https://www.relishapp.com/rspec/rspec-expectations/v/2-3/docs/custom-matchers/define-matcher
[4]: http://ruby5.envylabs.com/episodes/431-episode-395-august-16th-2013/stories/3493-renderable-null-objects "Ruby 5: All the hipster developers are using Null Objects ..."
[5]: http://www.poodr.info/ "Practical Object-Oriented Design in Ruby"
[6]: https://twitter.com/sandimetz
[7]: http://rubyscience.com
