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