Spock: Making Java Testing Groovy

In my previous post, I alluded to how I was introduced to the Ruby cult some time ago. I decided to try out the language for myself. Now, being used to statically typed languages, I’m not really a fan of type enforcing only happening at run-time for languages such as Ruby, JavaScript and Python. But one I really like about Ruby, similar to what I found about Python some time ago, is how human-readable it is, making understanding what the code does a lot easier. Groovy, a language that runs on the JVM also has this advantage to it, making it a useful tool in testing Java code.

People who are a lot smarter than me have mentioned that a value in testing is that it becomes a specification for what the actual code should do. The only way to actually know what an application does is by looking at the code, and a really good way to find out what it is supposed to be doing is by looking at the tests (assuming tests have been written).

The difficulty with testing frameworks for statically typed languages such as Java and C# is that in many cases, the tests are hard to understand, making it difficult for someone who wants to know what the code is supposed to be doing. Firstly, it is not naturaltoreadwordswrittenlikethis and even if you can, suchMethodNamesCanOnlyBeSoLongBeforeTheyGetTiring. A second thing that makes tests difficult to understand is that many developers don’t mindfully follow a test structure. When writing code, the more natural structure for the developer writing the test is the arrange-act-assert structure, but when reading, the given-when-then structure is a lot simpler to understand. As said before, more time is spent reading code that has been written than actually writing it.

A few days ago, Zorodzayi Mukuya introduced me to the Spock testing framework. Tests are written using the human-readable Groovy language, and because it runs on the JVM, these tests can be used to test Java code. I’ll go through a basic example of what it looks like, using an example of lending books to others, and recording this (especially for me who keeps forgetting who I lend books to).

Imagine you have a book class as follows:

public class Book { 
    private String isbn; 
    private String title; 

    public Book(String isbn, String title) { 
        this.isbn = isbn; 
        this.title = title; 
    } 

    public String getIsbn() { 
        return isbn; 
    } 

    public String getTitle() { 
        return title; 
    } 
}

Now, I would like to add borrowing functionality to this class. So I will create a test that allows me to retrieve the current holder of the book. Using the Spock framework with the easy-to-read Groovy language, this may look like this:

def "When a book is loaned, the current holder should be the borrower"() { 
    given: "There is a book" 
    def book = new Book("test-isbn", "Test Book Title") 

    when: "The book is loaned" 
    book.loanBook("Xolani Gwala") 

    then: "The current holder should be the borrower"   
    book.getCurrentHolder() == "Xolani Gwala" 
}

The test is defined in a normal speakable language format, making it easier to know what the code should be doing, as well as what it isn’t doing when the test fails. The ‘given’ block defines the pre-conditions for the action that is to be tested. The ‘when’ block describes the action undertaken. The ‘then’ block describes the expected result. Even a baby could understand this. Maybe not. But you get my point.

Now, the test would obviously fail because such a method doesn’t exist yet. To implement the method, and make the test pass, one can add the following method to the book class:

private String borrower; 
public void loanBook(String borrower) { 
    this.borrower = borrower; 
} 

public String getCurrentHolder() { 
    return this.borrower; 
}

Amazing! Now we know who holds the book. (Aside: If you’re reading this and you have my copy of Outliers by Malcolm Gladwell, please return it). When we have a book object, we might also want to know what the current status of the book is. So we could create and enum like the following:

public enum LoanStatus { IN_POSSESSION, LOANED }

and we could add a test to ensure that the status is LOANED when we borrow the book:

def "When a book is loaned, the current status should be Loaned"() { 
    given: 
    def book = new Book("test-isbn", "Test Book Title") 

    when: 
    book.loanBook("Xolani Gwala") 

    then: 
    book.getCurrentStatus() == LoanStatus.LOANED 
}

As can be seen in the test above, the descriptors in the given, when and then blocks aren’t mandatory. In the first test example, they were a bit trivial. Therefore they can be used when the code in the blocks is a bit difficult to understand.

The test would fail, and the implementation to fix the test could look like this:

private LoanStatus loanStatus; 
private String borrower; 

public void loanBook(String borrower) { 
    this.borrower = borrower; 
    this.loanStatus = LoanStatus.LOANED; 
} 

public LoanStatus getCurrentStatus() { 
    return this.loanStatus; 
}

Awesome! Now what happens when someone returns the book? We could first write the tests as follows:

def "When a book is returned, the current status should be In Possession"() { 
    given: 
    def book = new Book("test-isbn", "Test Book Title") 
    book.loanBook("Xolani Gwala") 

    when: 
    book.returnBook() 

    then: 
    book.getCurrentStatus() == LoanStatus.IN_POSSESSION 
} 

def "When a book is returned, the holder should be null"() { 
    given: 
    def book = new Book("test-isbn", "Test Book Title") 
    book.loanBook("Xolani Gwala") 

    when: 
    book.returnBook() 

    then: 
    book.getCurrentHolder() == null 
}

And to implement the functionality to fix the failing test, add the following functionality to the Book class:

public void returnBook() { 
    this.borrower = null; 
    this.loanStatus = LoanStatus.IN_POSSESSION; 
}

Done. Easy. Simple. As can be seen, we have the statically typed nature of the Java language and all the advantages it brings for the production code. At the same time, we have the readability of the Groovy language and the Spock testing framework for the tests, making them much easier to understand and a simpler specification for the production code.

Patrick Kayongo

I create and maintain software. Pan-African.

Johannesburg, South Africa