Test Doubles, Dependency Inversion Principle (DIP) Comments

At some point, you know I had to write this article. Let me state something clearly from the beginning: in spite of the title, I use mocks freely and happily.

I do not intend with this article to join those who either shun mocks or appear to shun mocks only to praise them. I plan to share a simple example of using mocks as a stepping stone towards a potentially more suitable design, but you must not interpret this as a message against mock objects.

There. I feel better now. I trust you to carry my message the way I intended it. Do not betray that trust.

First, let’s assume that you agree to the general principle that increasing testability tends to improve design, and in particular, helps us deliver features less expensively and with more predictable cost. If you don’t believe that, then what follows will likely not interest you.1 Now that we agree on that basic principle, I tend to get better results by writing repeatable tests, meaning that running the same test several times without any code changes gives the same result (pass or fail). This repeatability property helps me trust my tests more, and also helps me trust my production code more, as I have fewer unexpected, nasty surprises. This leads me to follow the simple guideline:

Write more repeatable tests. (Tests with more repeatability and more of them.)

Threats to Repeatability

Certain design decisions threaten the repeatability of tests, such as depending on configuration data, databases, web services, the location of certain files with certain content, and even the layout of the file system (Windows or Unix). All these external dependencies threaten to change the results of a test run even though the underlying production code has not changed. These choices make it more difficult to reason about the code, to find the source of a mistake, or to assess the impact of a change, because the behavior of the system can change without warning. While implicit dependencies hurt, implicit dependencies on unstable resources hurt more. In order to write more repeatable tests, I limit the amount of code that depends on unstable resources.

Unstable dependencies threaten repeatability. Implicit dependencies threaten our reasoning about the code. Unstable, implicit dependencies threaten our sanity.

Our Favorite Unstable Dependency

We work quite frequently with one common unstable dependency: the system clock. We do this whenever we write code that depends on a timestamp or on a random-number generator.2 Even when our code depends only a single timestamp, it probably does so by invoking the now() function, whatever you call it in your language of choice. I can remember writing Transaction Scripts (see the references below to learn more about this topic) in web applications that retrieve, for example, all the customers with orders placed in the preceding seven days, a straightforward version of which looks like the following Python-like code. (Not intended to be working nor optimized code, but lovingly improved with the help of George Paci.)

class CustomersWithRecentOpenOrdersController:
  def handle_request(self, request, response):
    now = Date()
    seven_days_ago = now - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, now))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

As soon as this code invokes Date() it not only obscures the way to compute now, but it also creates a direct, hardwired dependency on an unstable, global resource: the system clock. We can almost never run this code twice and get the same result, at least not without considerable effort. (I’m waiting for the day that someone includes “set the time to 14:30” in their puppet instructions.)

“No problem!”, you shout with delight. Dependency Injection to the rescue! We shall use the Virtual Clock pattern! Yes, we absolutely can, and I still think fondly of Paolo Perrotta’s delightful article on the topic—an article which appears to have disappeared from the web in the intervening years. What a shame! Let me summarize the Virtual Clock pattern for you.

The Virtual Clock

In order to more easily test code that depends on the system clock, we might inject an interface Clock that allows the code to work both with a clock whose time we control and the system clock. It sounds simple enough, and if we do this, then our code changes to something like the following.

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository, clock):
    self.orders_repository = orders_repository
    self.clock = clock or SystemClock.new # in case clock is None

  def handle_request(self, request, response):
    now = self.clock.now()
    seven_days_ago = now - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= %d and created_at <= %d" % (seven_days_ago, now))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

Here we introduce an interface (or protocol, in the case of languages without explicit interfaces, like Python) representing a clock with the appropriate methods.

class FrozenClock:
  def __init__(current_time_millis):
    self.current_time_millis = current_time_millis

  def now(self):
    Date(self.current_time_millis)

class SystemClock:
  def __init__():
    pass  # I'll just talk to the system clock directly as a global resource, if you don't mind

  def now(self):
    Date()

With this new protocol, we can easily freeze the clock to any time that our various tests find convenient. You could stub the Clock interface with your favorite test double library, if you prefer. The Virtual Clock solves the repeatability problem.

Something Smells

In spite of this, something feels wrong about the design. The code has become more flexible, but also more complicated, and I don’t feel3 particularly good about the dependency that we’ve introduced. We’ve made an implicit dependency more explicit, and I value that a lot, but Clock somehow feels like indirection without abstraction. By this I mean that we’ve introduced a seam4 to improve testability, but that the resulting code exposes details rather than hiding them, and abstractions, by definition, hide details. This looks like the kind of indirection that makes experienced designers look at testability nuts like us with suspicion. Rightly so, I have begun to conclude.

I used to think that “they” had it wrong. I even felt really confident about that. Now I see their point better.

If we want to introduce indirection, then we ought to introduce the smallest indirection possible. And we absolutely must introduce proper abstraction.

Indirection costs: it adds code, it adds cognitive load, it adds complications, and complications kill. I prefer to keep those costs low. In this example, Clock feels to me like an artificial “abstraction”, and as a result, I’d rather do something else.

Where Are My Keys?

Imagine that I want to hand you my keys, and that you stand right next to me. Imagine now that, instead of handing my keys to you, I walk across a ten-meter-long room, put my keys on the table, walk back, then tell you, “You’ll find my keys over there on the table.” I couldn’t imagine having a good reason to do this, but we do exactly this when we introduce Clock. Instead of giving you now, I’m giving you instructions for how to find now. Sometimes we need that. This does not feel like one of those times.

Indeed no. Instead, I offer you another alternative.

Don’t Inject What You Can Avoid

You’ve probably heard this before: don’t automate what you can eliminate. Similarly:

Don’t inject what you can avoid.

Why inject Clock? We really only want the clock because we want now. We could take a page from our functional programming friends and inject, you know, a function.

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository, now = lambda: Date()):
    self.orders_repository = orders_repository
    self.now = now

  def handle_request(self, request, response):
    now = self.now()
    seven_days_ago = now - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, now))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

Now our test can create the controller with lambda: Date(whatever_timestamp_we_want). If you program in C# then you already had to control your laughter at seeing a one-method interface where obviously a delegate would do the job. If you program in Java 8, then you just knew that this code needed a lambda all along. Even so, we can do even better.

A Minor Problem

This code respects the Interface Segregation Principle better, but it still suffers from a serious design problem that could trip you up during a heated, future refactoring session. Imagine what happens if you try to inline the temporary variable now.

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository, now = lambda: Date()):
    self.orders_repository = orders_repository
    self.now = now

  def handle_request(self, request, response):
    seven_days_ago = self.now() - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, self.now()))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

When you invoke now() more than once, you’ll get a different value for now each time. At some point, this will cause a problem.

Of course, we solve this problem by introducing a temporary variable to store the value of now right at the beginning of the function.

Wait… what?! If you pass me object a, and the first thing I do with it is ask it for value b so that I can operate on it, then what did I need a for in the first place?! Why didn’t I just ask for b?! There’s a clue here.

Don’t Inject What You Can Avoid Redux

Why inject a function returning 12 when we can inject 12? Yes, in the future, we might need to inject something that computes 12, but for now, we just need the 12, so we owe it to ourselves to try injecting 12 and seeing how that changes our perception of the design.

12. (Annoyed yet?)

I’ll defactor this code just a little more and you’ll probably find it strange.

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository, now):
    self.orders_repository = orders_repository
    self.now = now or Date() # in case now is None

  def handle_request(self, request, response):
    seven_days_ago = self.now - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, self.now))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

I find it strange, too. I’ve simplified the dependency in one respect, and complicated it in another: clients have to instantiate a new controller on each request, or said differently, the controller has request scope. This sounds wrong.

Moreover, one typically doesn’t talk about “injecting” values; one simply passes them as parameters.7 It should pose no problem, then, to reduce the scope of now to the method… except that we live in a framework—didn’t I mention that?—where we have to implement the method handle_request(self, request, response) in order to register our controller to receive requests for the URI /customers/recent_orders. This limitation creates tension: I want now as a parameter to the request handler, but the framework doesn’t want to (and couldn’t possibly) know this domain-level detail. (Or could it? Think about it.) This encourages me to extract a new method as a compromise.

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository):
    self.orders_repository = orders_repository

  def handle_request(self, request, response):
    return handle_request_as_of(self, request, response, Date())

  def handle_request_as_of(self, request, response, now):
    seven_days_ago = now - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, now))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

Now steel yourself for a shock: I don’t intend to test handle_request(). Don’t panic. I will test handle_request_as_of() with great care and attention and trust that I typed Date() correctly. This improves the design in one obvious way: I can test handle_request_as_of() quite easily, by passing a variety of values for now. Moreover, we can reconsider the name now, replacing it with something less context dependent, and call it instant (my current favorite default name for a timestamp).

class CustomersWithRecentOpenOrdersController:
  def __init__(orders_repository):
    self.orders_repository = orders_repository

  def handle_request(self, request, response):
    return handle_request_as_of(self, request, response, Date())

  def handle_request_as_of(self, request, response, instant):
    seven_days_ago = instant - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, instant))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

I choose to ignore for the moment the issue with assuming that instant is a number representing time in milliseconds. If you and I paired on this for a real client, we’d have fixed that by now, so that the code could read something more like instant.minus(Days(7)).

I like that this new method now finds “customers with recent open orders, relative to any date you choose”, rather than specifically relative to now. Come to think of it, “recent” now feels a little out of place, but I don’t want to go too far down that rabbit hole, so write down that we should consider improving the name of “recent” and we’ll come back to that.

The Instantaneous Request

We have given birth to a new abstraction in our web application framework: the instantaneous request. This object represents a request that we handle by pretending that it happens all at once (instantaneously). We will probably find this abstraction useful in other parts of the system. We might even extract it into a request handler decoration that we can use. Of course, we will probably wait until we have three requests before we bother extracting the duplication. When we do, we’ll have something like this:

# Reuse me anywhere
class InstantaneousRequestController:
  def __init__(instantaneous_request):
    self.instantaneous_request = instantaneous_request

  def handle_request(self, request, response):
    self.instantaneous_request.handle_request_as_of(self, request, response, Date())

# I belong to the domain
class CustomersWithOpenOrdersAsOfInstantController:
  def __init__(orders_repository):
    self.orders_repository = orders_repository

  def handle_request_as_of(self, request, response, instant):
    seven_days_ago = instant - 7 * 24 * 60 * 60 * 1000
    recent_open_orders = self.orders_repository.find_all_open_orders("created_at >= date(%d) and created_at <= date(%d)" % (seven_days_ago, instant))
    response.attributes["customers_with_recent_open_orders"] = recent_open_orders.map(lambda order: order.customer).uniq
    response.view = "RecentOpenOrdersReport"

When it comes time to register this controller to respond to the appropriate request URI, register this:

InstantaneousRequestController(
  CustomersWithOpenOrdersAsOfInstantController(
    ProductionCaliberOrdersRepository()))

If you’d feel more comfortable giving this a name, then feel free.

class CustomersWithRecentOpenOrdersController(InstantaneousRequestController):
  def __init__(orders_repository):
    super(CustomersWithOpenOrdersAsOfInstantController(orders_repository))

The Whole Point

Did you notice the comment Reuse me anywhere? Did you notice the key word in there?

Reuse.

When we build modules (functions, objects, whatever) with less knowledge of their surroundings, we naturally build modules more fit for reuse. No more excuses. If you want more reuse, you have to make it happen, and building modules with context independence in mind achieves that aim. We got there by noticing indirection without abstraction, then inverting the dependency (which happened to remove a mock object), then spotting duplication, then extracting that duplication into a context-independent, reusable module.

We did that. No magic. “Objects” didn’t even matter here. We could have done the same thing in Haskell with functions.

So please stop telling me that object-oriented programming hasn’t fulfilled its promise of increasing code reuse. Programmers haven’t fulfilled their promise of increasing code reuse.

Write code more independent of its context. I find this code easier to test, easier to understand, easier to maintain, more stable, and easier to reuse.

Try it!

One More Thing…

If you just don’t like passing instant as a parameter here, and prefer something more flexible, then I’d like to suggest an alternative to the Clock protocol. Clock feels like a leaky abstraction: although it provides some flexibility in the implementation details, its name reflects an unnecessary assumption about what matters to the code. The code doesn’t want a clock; it wants a stream of timestamps. So give it a stream of timestamps. You could pass an InfiniteIterator of Timestamp objects (or whatever you call them in your programming language of choice). This represents a true abstraction, because it says, “My code needs some timestamps. I don’t know how many it needs and I don’t even care what they represent. My code just needs an endless stream of timestamps.”

A frozen clock? That’s just an endless stream of the same timestamp over and over again. A system clock? That’s just an endless stream of calls to Date(). Tempted to use TimeCop to control the system clock? No need. Just provide an endless stream of whatever timestamps you need. And it all just works like an iterator.

That’s abstraction.

References

Martin Fowler, “Transaction Script”. I often implement my controllers as Transaction Scripts, then as I notice duplication, I start to separate application logic from business logic, moving the application logic up the call stack into “filters” and moving the business logic down the call stack into a Domain Model.

Martin Fowler, Patterns of Enterprise Application Architecture. A catalog of fundamental patterns in enterprise applications that you likely take for granted by now.

J. B. Rainsberger, “Injecting Testability Into Your Designs”. An older article in which I describe a simple example of injecting dependencies new to the concept.

Michael Feathers, Working Effectively with Legacy Code. This remains the standard reference for maintaining and living with legacy code.

Reactions


  1. If you feel open to learning more about this point, please take a few minutes to read “The Eternal Struggle Between the Business and Programmers”.

  2. I assume that pseudo-random number generators still usually depend on the system clock to generate a seed. If that has changed, then please point me to an article or two on the topic. I’m getting old.

  3. I have learned to pay attention to my intuition as a software designer, but mostly because I’ve developed that intuition through over a decade of constant, careful practice.

  4. I learned the term seam from Michael Feathers’ work on legacy code. A seam provides a place to plug alternative implementations of functions that initially make testing more convenient, but almost always end up improving the design of the system, too.

  5. We call an operation referentially transparent if it evaluates to the same value every time. Functionally-minded programmers make a big deal out of this for good reason, as it contributes to repeatability.

  6. My good friend Michael Bolton defines “bug” as “something that bugs someone” and “issue” as “something that threatens the value of the system”.

  7. This illustrates why some people pompously label dependency injection as an overblown consultant-driven marketing phrase. “Real programmers just pass parameters”, they say. Indeed, but I find some value in differentiating passing values to a function and passing collaborating services to a service. I pass values, but I inject collaborators. I don’t see the problem.

Comments

Design credit: Shashank Mehta