Design Patterns – Unit Testability of Builder Pattern

design-patternsunit testing

I'm looking at the builder pattern for helping setting up dependencies and parameters that may require complex logic for a class. But from the examples I've seen, the builder pattern does not seem very testable. Example I took from here:

 public static class Builder {
    private long accountNumber; //This is important, so we'll pass it to the constructor.
    private String owner;
    private String branch;
    private double balance;
    private double interestRate;
    public Builder(long accountNumber) {
        this.accountNumber = accountNumber;
    }
    public Builder withOwner(String owner){
        this.owner = owner;
        return this;  //By returning the builder each time, we can create a fluent interface.
    }
    public Builder atBranch(String branch){
        this.branch = branch;
        return this;
    }
    public Builder openingBalance(double balance){
        this.balance = balance;
        return this;
    }
    public Builder atRate(double interestRate){
        this.interestRate = interestRate;
        return this;
    }
    public BankAccount build(){
        //Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
        BankAccount account = new BankAccount();  //Since the builder is in the BankAccount class, we can invoke its private constructor.
        account.accountNumber = this.accountNumber;
        account.owner = this.owner;
        account.branch = this.branch;
        account.balance = this.balance;
        account.interestRate = this.interestRate;
        return account;
    }
}

All the functions return the builder and set private members the builder class so it's hard to test if the functions are correct. In this case, maybe it's fine because the setter logic is very simple so it does not need to be tested. But what if the functions need to do more complex work to setup the parameter for the class being built? For example, say the function receives a list and needs to filter and group some values to create a dictionary.

How is the builder pattern commonly tested?

Possible ways I see to make it more testable:

  1. Move all the logic into another class and the builder simply calls that class. Downside is it seems to make the builder a lot less useful.

  2. Test it by inference through the class that is being built. But this becomes more of an integration test and can get very complex.

Best Answer

The one-and-only responsibility of a builder is clear: to create an object, for example, a BankAccount object. The ultimate test for the correctness of this task is the final state of that object, how the builder works internally is an implementation detail which should not be tested.

So the straightforward way of testing a builder is by calling its methods and checking the output of the build method. I am sure in your example the BankAccount has public "getters" for each interesting member, so their content should be very easy to validate in a test.

But what if the functions need to do more complex work to setup the parameter for the class being built?

That depends what "more complex work" means. Sometimes it is sufficient to add more tests. A code coverage and/or branch coverage analysis could help to pick those tests. When the builder itself uses a very complex algorithm for doing its job, that complex algorithm maybe worth a class of its own, which then can be unit tested on its own. But I would not stress this approach until it is really required.

And to your comment:

one issue I see with trying to test the final state through the built class's members, is that it means those members have to be public or have getters

The internal state of any meaningful object is almost always somehow read-accessable:

  • either directly through public getters

  • or indirectly by some influence on the behaviour of other public methods

Otherwise, the internal state would be superfluous. So looking at the public interface of the constructed object you will normally find a way for validating the content.