Ostia

Using the Builder Pattern for Test Fixtures

| Comments

If you read some of my previous blog posts, you may have noticed I’m quite keen on TDD. Writing good tests and writing them first is the only way I know to:

  • prove your code works the way you expect it to
  • ensure a bug you fix now will never find its way back into production
  • properly document your code without the inherent duplication of code comments
  • build a good safety net for refactoring
  • sleep at night

In theory, test methods should be concise, and they should have a given-when-then structure (test setup, actual call to production code, result verification). However, Yogi Berra is right when he says there is no difference between theory and practice in theory, while in practice there is.

So, in this post I’ll try to close that gap between theory an practice a bit, and show why using the builder pattern is a great(*) way to write test fixtures.

The Builder pattern

As a reminder: the builder pattern is a creational design pattern, where a class (a Builder) is created that is responsible for gathering a bunch of data, and, well, build an object of a given type based on the data, just in time. We’ll be creating such a builder class for each of our domain objects.

tl;dr ?–> here’s the code.

These builders can then be used for test setup, as in:

Why is this interesting for tests

Let’s zoom in a bit on the lines above, and see why these builders make our test setup elegant:

  • new PersonBuilder().build() gives me sensible default object: Because we initialized the instance variables in PersonBuilder to something sensible, I can just write aPerson().build() and be done with it. This also allows me to just use the withXXX methods that are relevant for the current test, and not care about the others.
  • static builder factory methods: Both AddressBuilder and PersonBuilder have a static factory method to instantiate builders. aPerson().withName("someName") is a lot easier on the eyes than new PersonBuilder().withName("someName"), is it not? Well maybe not a lot, but every bit helps ;)
  • overloaded withXXX() methods : lines 37 and following all allow us to configure a PersonBuilder instance with addresses, even by providing a vararg array of AddressBuilders. Handy, right?
  • we only initialize state: these builders use setters, since our Person and Address objects are normal mutable java beans. If you want to build (immutable) value objects, you still can. Just use reflection (and a good IDE </troll>). I can highly recommend Mockito’s WhiteBox.setInternalState().
  • method chaining: withXXX() methods return the builder instance itself, so it’s possible to write chained method calls.

Caveats

Builders should set state, nothing more.

It may be tempting to have something like this in your build() method:

After all, you have the addAddress method already, so why not use it?

The problem with this is that you’re using production code (the addAddress() method) with possible side effects. You assume it doesn’t, but you don’t know unless you go read the code. Another, even more important reason is that your PersonBuilder may well be used itself to test the addAddress() method. So it doesn’t make a lot of sense to be calling it indirectly in your test setup.

Constructors that “do stuff”

The ‘Only set state’ argument goes for constructors too. In every build() method, you’ll have to instantiate the object you want to build. Here too, you’ll want to just set the instance variables on the object that is being built, nothing more.

So, avoid calling constructors that contain logic from your build() method. If need be, provide a package local no-args constructor on your production class and annotate it with @VisibleForTesting (or not).

Beware of bidirectional relationships and object graphs

The builder pattern is a near-to-perfect tool for building object hierarchies (trees, that is). Graphs, on the other hand, are a bit harder to build, since you’ll have to respect a certain order in which to build your objects. Sounds easy perhaps, but it isn’t, believe me. In fact, I think this topic alone could provide enough material for a separate blog post :)

Until next time!
— Ben

(*) Using builders for test fixtures is not an original idea, and it’s certainly not mine. However, I have used this on multiple projects before, and it works better than loading large, hard-to-change xml files.

Comments