The previous post introduced the concept of the Rich Test Model. This article shows the source code of the model and how it can be enriched with further behavior in order to support more test scenarios.
The TransferTestModel source code looks like this:
public class TransferTestModel {
private Account debitAccount;
private Account creditAccount;
private Date transferDate;
private Money transferAmount;
private TransferTestModel() {} // not instantiable
public static TransferTestModel startTransferFromPrivateAccount() {
return new TransferTestModel() {
{
debitAccount = new PrivateAccount(
"1122334455", new Money("CHF", 5000));
}
}
}
public TransferTestModel toForeignBankAccount() {
creditAccount = new GenericBankAccount(
"9988776655", new Address("Money Receiver", "1234", "Somewhere"));
return this;
}
public TransferTestModel asSoonAsPossible() {
transferDate = DateUtils.nextBusinessDay();
return this;
}
public TransferTestModel amountGreaterThanAvailableMoney() {
transferAmount = debitAccount.getBalance().add(1);
return this;
}
public Transfer build() {
return new Transfer(
creditAccount, debitAccount, transferDate, transferAmount);
}
}
This class follows the Builder pattern and consists of three parts:
- The static (factory) method startTransferFromPrivateAccount starts the build process. (mandatory data)
- The three member methods toForeignBankAccount, asSoonAsPossible, and amountGreaterThanAvailableMoney define the transfer details. (optional data)
- The build method finishes the build process and returns the Transfer object.
Even though the TransferTestModel has only one static and three member methods, it already supports eight different test scenarios:
= <number of static methods> * (2 ^ <number of member methods>)
By adding
- new static factory methods (e.g. for account types applying different business rules) and
- new member methods (e.g. for further business aspects, threshold testing, or special cases which need complex initializations),
the model will be enriched and more test scenarios are possible.
Assume we want to test transfers from a savings account (with a different “available money” policy), transfers scheduled on a weekend day, and transfers scheduled in the past. Let’s see how the model will be expanded…
public class TransferTestModel {
public static TransferTestModel startTransferFromSavingsAccount() {
return new TransferTestModel() {
{
debitAccount = new SavingsAccount(
"12356789", new Money("CHF", 150000));
}
@Override
public TransferTestModel amountGreaterThanAvailableMoney() {
// a maximum of CHF 50'000 can be transferred
// from a savings account
transferAmount = new Money("CHF", 50001);
return this;
}
}
}
public TransferTestModel onWeekendDay() {
Calendar cal = Calendar.getInstance();
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
int offset = Math.min(1, Calendar.SATURDAY - dayOfWeek);
cal.add(Calendar.DAY_OF_MONTH, offset);
transferDate = cal.getTime();
return this;
}
public TransferTestModel inThePast() {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, -1);
transferDate = cal.getTime();
return this;
}
// snipped code of first sample (see above)
}
The startTransferFromSavingsAccount method initializes a transfer using a savings account and overrides the default amountGreaterThanAvailableMoney method. Of course, a real-life test model would not use hardcoded amounts and policy rules (as used in the example above) but would rather delegate this to the business layer.
By overriding the default implementation, the static factory method defines and therefore encapsulates the (context bound) test behavior. A unit test based on this new method does not care about these details:
public class TransferServiceTest {
@Test
public void testTransferUsingInvalidAmount() {
Transfer transfer = TransferTestModel
.startTransferFromSavingsAccount()
.toForeignBankAccount()
.asSoonAsPossible()
.amountGreaterThanAvailableMoney()
.build();
Transaction transaction = transferService.transfer(transfer);
// Assert expected transaction details
}
}
This test only denotes that it wants to transfer an “invalid” amount but does not know what “invalid” means in terms of a savings account – this knowledge is hidden in the test model.
Outlook
A further article will focus on test assertions and method naming… stay tuned!

Very nice idea! “Testing Is Fun” will slowly come true… I’m curious to read more on this!
Cheers Mischa