Skip to main content

Testing Toolbelt: Groovy

Groovy, the apparent golden child of Java, is a mostly dynamic-typed JVM language which seeks to add functionality to the core Java language such that writing and testing code is much easier and requires less boiler-plate code. Some of the features provided by Groovy that I have found useful for testing are:

GroovyBean Properties and Constructors

Groovy attempts to simplify the usage of bean-type object by providing some shortcuts for accesses and assignments. By default, every class accessed through Groovy code is decorated with a default constructor which takes a map (property->value), and a getter and setter for each field defined in the class (for which there is not already a getter/setter). The constructor is a simple way to provide values to properties of the new instance.

properties-sample.groovy

class Customer {
  Integer id
  String name
  Date dob

  static void main(args) {
    /* note: the next statement is functionally equivalent to:
     * in Groovy:
     * def customer = new Customer()
     * customer.id = 1
     * customer.name = "Gromit"
     * customer.dob = new Date()
     * in Java (provided all of the set<name> methods were defined):
     * Customer customer = new Customer();
     * customer.setId(1);
     * customer.setName("Gromit");
     * customer.setDob(new Date());
     */
    def customer = new Customer(id:1, name:"Gromit", dob:new Date())
    // note: calls the getName() method defined below
    println("Hello ${customer.name}")
    // note: references the id field directly
    println("Your id is ${customer.id}")
    // ...not really, the Groovy compiler adds in a
    // default getter for the field.
  }

  public String getName() {
    println "in getter"
    return name
  }
}
Token Replacement

In Groovy, the String class is enhanced to allow for the use of tokens within a String literal. These tokens are of the form ${<property>} and will use the properties values available at the time of creation. Refer back to the above example to see some at work.

Collection Literals

Easily create a list by using the [] syntax, a range by using the <start>..<end> syntax, and a map by using the [<key>:<value>] syntax.

collection-sample.groovy

  def emptyList = []
  def list = [1, 2, 3, 4, 5, 6, 7, 8]
  def range = 1..8
  assert list == range
  def emptyMap = [:]
  def map = [1:2, 3:4, 5:6]
Closures

Closures are simply blocks of code surrounded by curly braces, which can be treated as reference types. Closures may declare arguments and may return values. Closures may be stored in variables.

closure-sample.groovy

  // times is actually a method which takes a number
  // (the target of the call) and a closure
  // print 0 to 10 using a closure
  10.times {int var -> println var} // explicit closure variable and type
  10.times {var -> println var} // explicit closure variable
  10.times {println it} // no variable declaration
Iteration (each)

Iteration of arrays, ranges, lists, and maps has been simplified by the addition of the each method.

each-sample.groovy

  // each is actually a method which takes an iterable
  // (the target of the call) and a closure
  // print 0 to 10 using a closure and a range (also an iterable)
  (0..10).each {int var -> println var} // explicit closure variable and type
  (0..10).each {var -> println var} // explicit closure variable
  (0..10).each {println it} // no variable declaration
  // print a map
  [1:10, 2:20, 3:30].each {key, value -> println "${key}-${value}"}
Coercion (Map and Closure)

Any map or closure may be interpreted as an instance of a specific type simply by using as <type>. This allows you to easily create stand-in objects, provided they have a public default (no-arg) constructor.

coercion-sample.groovy

  def map1 = {println "hi"} as Map
  map1.get("george") // prints hi
  map1["george"] // different way of doing the same thing in Groovy
  def map2 = [get: {key -> return "value"}] as HashMap
  map2["key"] = "hello"
  assert map2["key"] == "value"
GDK

Through the default meta-class implementations used by Groovy, many useful methods (many taking advantage of closures) have been added to the standard Java classes. There's too much to list here, but know that many of the above examples use GDK methods.

GroovyTestCase

The GroovyTestCase class contains a large portion of the testing functionality used by basic Groovy. Since the Groovy team have pledged backwards-compatibility with JDK 1.4, they have also stuck with the non-annotation-based version of JUnit(3.8.*) as a basis for their testing helper class. This is not as bad as it sounds, as many of the language features of Groovy make up for the missing capabilities of JUnit 4.4. Every test class defined using groovy must be a sub-class of GroovyTestCase. Each test method must be public, return void, and be named test<something>.

assert*

all of the assert* methods provided by JUnit are also present in the GroovyTestCase class.

shouldFail(<Throwable>)

similar to the Test annotation when used with the expected argument, the shouldFail method takes a Throwable class type and a closure, executes the closure, and fails if an instance of Throwable is not thrown within the closure.

shouldFail-sample.groovy

  shouldFail(ArrayIndexOutOfBoundsException) {
    println (0..4)[10];
  }
StubFor and MockFor

The StubFor/MockFor classes can be used within Groovy to create stand-in objects when coercion won't work or when you need to over-ride the behavior of a constructor.

stubfor-sample.groovy

  //can't use Map coercion, File does not have default constructor.
  def mockFileType = new MockFor(File)
  mockFileType.demand.exists { return true }
  mockFileType.use {
    def f = new File('DOES_NOT_EXIST.TXT')
    assertThat(f.exists, is(true))
  }

Comments