Ok, so right out of the bath: this entry is not about writing good tests. It’s not about writing clean tests either. This topic has been tackled on both in the past (like in the famous Clean Code book) and even quite recently on many occasions. No, it’s about doing the one more step, when your tests are understandable and easy to read and follow. It’s about checking the “what if”, experimenting with the form and tools and trying out new things, or at least in a new context. Long story short, it’s about putting the Groovy DSLs capabilities to work for your tests. I don’t claim to exhaust the topic either, simply writing down what has been working for me so far.

Traits as Test Fixtures

I guess that anyone that has spend enough time writing and maintaining tests knows about a test fixture and test fixture management strategies (see G. Mezaros xUnit patterns). For anyone that is not familiar with the pattern name - I’m simply talking about the factory methods used in tests that creates our System Under Test (SUT) in a fresh and defined state, This allows us to stay DRY and focus only on testing the expected behaviour. This is not the only way of managing test fixtures, but it’s my favourite (and probably the one that’s mostly used). When using jUnit, the most common way of sharing fixture factories is to create many static factory methods that can create the SUT in a particular state. In groovy we can use Traits. Both constructions give us very similar things, still in my experience Traits tend to do it in a more explicit way for the test reader. Let’s go over few of such issues:

  • Static factory methods can be reused in each other but with traits we can use the inheritance, so the dependency is clearly visible in the spec definition (and not somewhere in the imports)
  • Static factory methods can be reused in tests, but again the inheritance of traits allows the test reader spot that immediately
  • Traits support multi inheritance, so they are not limited in a same way as Java inheritance
  • Taits can be stateful. This might become tricky still, since the jUnit always recreates the Specification class instance before each test it’s relatively safe (although I would not encourage that, as in most cases things can be achieved without that)

All the above might not seem to be worth the hassle at first, but I guarantee that the change of approach does bring a long lasting benefits, especially when the code base growths, both in size and complexity. It’s worth to mention that similar visibility can be achieved in pure jUnit when using interfaces with default methods. Still the power of groovy DSLs goes well beyond that.

//Fixtures as static factory methods

class AccessLevelFixture {

    static AccessLevel grantAll() {...}
    
    static AccessLevel limitToCategory(String cat) {...}
}

import static AccessLevelFixture.grantAll
import static AccessLevelFixture.limitToCategory

class UserFixture {

    static User user(AccessLevel access) {...}

    static User fullAccessUser() {
        return user(grantAll())
    }

    static User oneCategoryUser() {
        return user(limitToCategory('CATEGORY_NAME'))
    }
}

//Fixtures as Traits
trait WithAccessLevel {

    AccessLevel grantAll() {...}
    
    AccessLevel limitToCategory(String cat) {...}
}

trait WithUser impements WithAccessLevel {

    User user(AccessLevel access) {...}

    User fullAccessUser() {
        return user(grantAll())
    }

    User oneCategoryUser() {
        return user(limitToCategory('CATEGORY_NAME'))
    }
}

Default parameters

It’s a very short one. On many occasions, when working with test fixtures factories, there is a need to create same SUT in many different ways, to support many test cases. When doing this through factory methods, we can either name each method explicitly or overload them. The approach depends on the context, but as a rule of thumb we can say that when the construction and meaning is simple and straight forward, we can go with explicit methods’ names, especially when they are short. However when the construction starts to grow in details, using overloaded names has the benefit of not hiding this complexity so we can express the meaning through our production code (which empowers TDD in general)

class WithUser {

    //Explicitly named methods

    public User fullAccessUser() {...}

    public static User userWithDeniedAccessToReports() {...}

    public static User userWithDeniedAccessToReportsAndBillingsAndWithPrincipal(User principal) {...}

    //Overloaded methods

    public static User user() {
        return user(Privileges.ALL);
    }

    public static User user(Privileges privileges) {
        return user(privileges, Hierarchy.top())
    }

    public static User user(Privileges privileges, Hierarchy principalHierarchy) {
        User user =  new User(...., privileges)
        user.attachTo(principalHierarchy);
        return user;
    }
}

When using groovy, we are not forced to explicitly overload all the methods, as we might simply use the default parameters. There is a limit in usability to this approach, but it’s always worth to start from it:

trait WithUser {

    User user(Privileges privileges = Privileges.ALL, Hierarchy principalHierarchy = Hierarchy.top()) {
        User user =  new User(...., privileges)
        user.attachTo(principalHierarchy);
        return user;
    }
}

@DelegatesTo Closures

Closures play an important role when creating a code that embraces DSLs and @DelegatesTo anotation allows compilation checks and IDE suggestions. It works somewhat like this:

class FooBar {

    static FooBar of(@DelegatesTo(FooBar) cfg) {
        FooBar fb = new FooBar()
        fb.with(cfg)
        return fb
    }
    
    void foo() {...}
    
    void bar() {...}
}


FooBar fb = FooBar.of {
    foo()
    bar()
}

This approach has multiple applications when creating DSLs and can be used as well and as easily to improve our TestFixtures, Assertions etc.


class CustomerActions {

    private final Customer customer
    
    @PackageScope
    CustomerActions(Customer customer) {
        this.customer = customer
    }

    void makesAnOrder(List<Item> items = [item()]) {
        this.customer.order(items)
    }
    
    void confirmsOrder() {
        this.customer.confirmOrder()
    }
    
    void withNumberOfConfirmedOrders(int orders) {
        orders.times {
            makesAnOrder()
            confirmsOrder()
        }
    }
}

trait WithCustomer {
    
    Customer customer(@DelegatesTo(UserActions) actions) {
        Customer c = customer()
        new CustomerActions(c).with(actions)
        return c
    }
    
    Customer customer() {...}
}

class UserSpecification extends Specification implements WithCustomer, WithClock {

    def 'customer receives reminder about not confirmed orders'() {
        given:
            Customer customer = customer {
                makesAnOrder()
            }
            
        when:
            aDayPassesBy() //time passes by, schedulers are executed etc.
            
        then:
            numberOfRemindersSentTo customer == 1
    }   
    
    private Integer numberOfRemindersSentTo(Customer customer) {}
}

This technique is used to create Customer in a proper state, that’s useful for the test and does not overload the reader with unimportant details regarding its construction. At first it might seem thatCustomerActions is mostly a wrapper over a standard operations avaiable by domain model, which is technically correct statement. Even though this example might seem a bit over engineered, methods void makesAnOrder(List<Item> items = [item()]) and void withNumberOfConfirmedOrders(int number) gives a hint of a more elaborate usages that actualy bring a lot of value in terms of readability and maintenance. By using default parameters, we define what does it mean to creat a minimally valid order (single item order in this case). From that moment, whenever that changes, we have exactly one place in the test code to align it to the new implementation. Let’s imagine that there is a new requirement:

As A customer service operator
In Order to not make empty promisses, for which we need to appologize later on
I want that whenever a customer makes an order availability of an item must be checked
And the order must be denied before confirmation, based on that availability  

This can be modeled in many different ways but for the sake of the argument let’s assume that we will introduce new domain service Availability that will be passed to order method in order to make this new rule explicit. The fixture changes accordingly:

class CustomerActions {

    private final Customer customer
    private final WithAvailability fixture
    
    @PackageScope
    CustomerActions(Customer customer, WithAvailability fixture) {
        this.customer = customer
        this.fixture = fixture
    }

    void makesAnOrder(List<Item> items = [item()], Availability availability = fixture.alwaysAvailable()) {
        this.customer.order(items, availability)
    }
    
    void confirmsOrder() {
        this.customer.confirmOrder()
    }
    
    void withNumberOfConfirmedOrders(int orders) {
            orders.times {
                makesAnOrder()
                confirmsOrder()
            }
    }
}

trait WithAvailability {

    Availability alwaysAvailable() {...}
}

Two things happens here:

  • client (test) code is not affected, so every place where we could’ve used this fixture (unit, integration, acceptance tests, other fixtures etc.) stays exactly the same, which dramatically decrease the cripling amount of changes connected with such change, as well as the anxiety connected with proposing such changes in the first place
  • using test fixture for Availability allows us to stay DRY and reuse the most common stubbings.

Default stubs and Mockito

When looking at the previous example, one might be thinking of how to deal with the Availability service inside the WithAvailability fixture. Most common answer would be to use a Stub pattern, however in most cases stubs are created in the test itself. This strategy of recreating stubs for each test that needs them would give us quite a lot of trouble in the long run. The simple implementation that recognizes any item as available will most likely be used for any positive scenario that considers making an order. We can easily use alwaysAvailable stub to go through test cases like: “makes a successful order”, “receives an email after making an order”, “receives a reminder after making an order without confirmation”, “receives a discount on every fifth order” and so on. None of these scenarios are interested in the Items availability, so all of them will be happy to assume that every item is simply always available, especially when there is no need for the test to even be aware of Availability problem. Saying that, it’s not far fetched to also say that keeping such Stubs in a separate fixtures allows to stay DRY in many places as well as being open to changes in this model in the future. However the question of how to implement this stub is still open. Depending on the complexity of the service one might start from lambda:

interface Availability {

    boolean isAvailable(Item item)
}

trait WithAvailability {

    Availability alwaysAvailable() {
        return {
            return true
        } as Availability
    }
}

In most of the cases though, using stubbing framework like Mockito will be a better idea.

interface Availability {

    boolean isAvailable(Item item)
}

trait WithAvailability {

    Availability alwaysAvailable() {
        return {
            return true
        } as Availability
    }
    
    Availability availableItems(List<Item> items) {
        Availability stub = Mockito.mock(Availability)
        items.forEach { Item item ->
            Mockito.when(stub.isAvailable(item)).thenReturn(true)
        }
        return stub
    }
}

There is a possibility to use Spock standalone stubs and mocks, however I was never successful in defining any behaviours for them outside of a Specification. As such they still can be used as a Dummy (another one of Test Double patterns) or in connection with DI framework (like Spring), with behaviours added inside the test scenario itself.

What’s in part 2?

This post has become to long, so I’ve decided to cut it somehow in half. Next time you can expect topics around monkey patching, coercion and parameters, so stay tuned.