Tuesday, January 13, 2009

Creating Objects in Java Unit Tests

Most Java unit tests consist of a Class Under Test (CUT), and possibly dependencies or collaborators. The CUT, dependencies and collaborators need to be created somewhere. Some people create each object using 'new' (vanilla construction), others use patterns such as Test Data Builders or Object Mother. Sometimes dependencies and collaborators are instances of concrete classes and sometimes it makes more sense to use Test Doubles. This entry is about the patterns I've found most helpful for creating objects within my tests.

In January of 2006, Martin Fowler wrote a quick blog entry that included definitions for the various types of test doubles. I've found working with those definitions to be very helpful.

Test Doubles: Mocks and Stubs
Mockito gives you the ability to easily create mocks and stubs. More precisely, I use Mockito 'mocks' for both mocking and stubbing. Mockito's mocks are not strict: They do not throw an exception when an 'unexpected' method is called. Instead, Mockito records all method calls and allows you to verify at the end of your test. Any method calls that are not verified are simply ignored. The ability to ignore method calls you don't care about makes Mockito perfect for stubbing as well.

Test Doubles: Dummies
I didn't find many cases where I needed a dummy. A dummy is really nothing more than a traditional mock (one that throws exceptions on 'unexpected' method calls) with no expected method calls. In cases where I wanted to ensure that nothing was called on an object, I usually used the verifyZeroInteractions method on Mockito mocks. However, there are exceptional cases where I don't want to verify that no methods were called, but I would like to be alerted if a method is called. This is more of a warning scenario than an actual failure.

On my current project we ended up rolling our own dummies where necessary and overriding (or implementing) each method with code that would throw a new Exception. This was fairly easy to do in IntelliJ; however, I'm looking forward to the next version of Mockito, which should allow for easier dummy creation.

Test Doubles: Fakes
My current project does make use of Fakes. We define a fake as an instance that works for and is targeted towards a single problem. For example, if your class depended on a Jetlang fiber you could pass in a fake Jetlang fiber that synchronously executed a command as soon as it was given one. The fake fiber wouldn't allow you to schedule tasks, but that's okay, it's designed to process synchronous requests and that's it.

We don't have a large amount of fakes, but they can be a superior alternative when compared to creating multiple mocks that behave in the same way. If I need a CreditCardProcessingGateway to return a result once, it's good to use a mock. However, if I need a CreditCardProcessingGateway to consistently return true when given a luhn valid credit card number (or false otherwise) a fake can be a superior option.

Concrete Classes
I'm a big fan of Nat Pryce's Test Data Builders for creating concrete classes. I use test data builders to create the majority of dependencies and collaborators. Test data builders also allow me to easily drop in a test double where necessary. The following example code shows how I'll use a test data builder to easily create a car object.
aNew().car().
with(mock(Engine.class)).
with(fake().radio().fm_only()).
with(aNew().powerWindows()).build();
The (contrived) example demonstrates 4 difference concepts:
  • A car can easily be built with a mock engine
  • A car can easily be built with a fake fm radio
  • A car can easily be built with a power windows concrete implementation
  • All other dependencies will be sensible defaults
Nat's original entry on the topic states that each builder should have 'sensible defaults'. This left things a bit open for interpretation so we tried various defaults. In the end it made the most sense to have all test data builders use other test builders as defaults, or null. We never use any test doubles as defaults. In practice this is not painful in anyway, since you can easily drop in your own mock or fake in a specific test that requires it.

The builders are easy to work with because you know the default dependencies are concrete, instead of having to look at the code to determine if the dependencies are concrete, mocks, or fakes. This convention makes writing and modifying tests much faster.

You may have also noticed the aNew() and fake() methods from the previous example. The aNew() method returns a DomainObjectBuilder class and the fake() method returns a Faker class. These methods are convenience methods that can be staticly imported. The implementations of these classes are very simple. Given a domain object Radio, the DomainObjectBuilder would have a method defined similar to the example below.
public RadioBuilder radio() {
RadioBuilder.create();
}
This allows you to import the aNew method and then have access to all the test data builders in a code completeable manner. Keeping the defaults in the create method of each builder ensures that any shared builders will come packaged with their defaults. You could also create a no-arg constructor, but I prefer each builder to have only one constructor that contains all the dependencies necessary for creating the actual concrete class.

The following code shows how all these things work together.

// RadioTest.java
public class RadioTest {
public void shouldBeOn() {
Radio radio = aNew().radio().build();
radio.turnOn();
assertTrue(true, radio.isOn());
}
// .. other tests...
}

// DomainObjectBuilder.java
public class DomainObjectBuilder {
public static DomainObjectBuilder aNew() {
return new DomainObjectBuilder();
}

public RadioBuilder radio() {
return RadioBuilder.create();
}
}

// RadioBuilder.java
public class RadioBuilder {
private int buttons;
private CDPlayer cdPlayer;
private MP3Player mp3Player;

public static RadioBuilder create() {
return new RadioBuilder(4,
CDPlayerBuilder.create().build(),
MP3PlayerBuilder.create().build());
}

public RadioBuilder(int buttons, CDPlayer cdPlayer, MP3Player mp3Player) {
this.buttons = buttons;
this.cdPlayer = cdPlayer;
this.mp3Player = mp3Player;
}

public RadioBuilder withButtons(int buttons) {
return new RadioBuilder(buttons, cdPlayer, mp3Player);
}

public RadioBuilder with(CDPlayer cdPlayer) {
return new RadioBuilder(buttons, cdPlayer, mp3Player);
}

public RadioBuilder with(MP3Player mp3Player) {
return new RadioBuilder(buttons, cdPlayer, mp3Player);
}

public Radio build() {
return new Radio(buttons, cdPlayer, mp3Player);
}
}

As you can see from the example, it's easy to create a radio with sensible defaults, but you can also replace dependencies where necessary using the 'with' methods.

Since Java gives me method overloading based on types, I always name my methods 'with' when I can (like the example shows). This doesn't always work, if for example you have two different properties that are of the same type. This usually happens with built in types, and in those cases I create methods such as withButtons, withUpperLimit, or withLowerLimit.

The one other habit I've gotten into is using Builders to create all objects within my tests, even the Class Under Test. This results in more maintainable tests. If you use the the Class Under Test's constructor explicitly within your test and you add a dependency you'll end up having to change each line that creates a Class Under Test instance. However, if you use a builder you may not need to change anything, and if you do have to change anything it will probably only be for a subset of the tests.

Conclusion
I'm a big fan of Test Data Builders and Test Doubles. Combining the two concepts has resulted in being able to write tests faster and maintain them more easily. These ideas can be incrementally added as well, which is a nice bonus for someone looking to add this style to an existing codebase.
Post a Comment