Tomas has posted 4 posts at DZone. You can read more from them at their website. View Full User Profile

Writing unit tests using Groovy

03.06.2008
| 20220 views |
  • submit to reddit

Groovy can greatly decrease the level of work involved in creating unit tests for your Java code. I find that this is especially evident in the number of code lines I need to write to create the data objects that are needed for the tests. I often end up writing up towards two hundred lines of Java to create a decent amount of test data. Using Groovy I can reduce this to around 50.

This article will show how to create a unit test in Groovy that is automatically integrated into the Maven test suite and executed when running the build. I will also give a real life example to illustrate how much less code is needed when creating the supporting data for a test.

To complete the toolbox I will also illustrate how to use EasyMock to mock dependencies in a Groovy test case.

Configuring Maven2 to compile and run your Groovy tests.

First of all, in order for the tests to execute we will need to configure the Maven2 configuration to use the Groovy plug-in in it's test phase.

To do this, add the Groovy jar dependency to the pom file and assign it the correct scope. Add the below snippet in-between the <dependencies> tags:

<dependency>
<groupId>org.codehaus.mojo.groovy.runtime</groupId>
<artifactId>groovy-runtime-1.1</artifactId>
<version>1.0-beta-3</version>
<scope>test</scope>
</dependency>

Then add the following snippet in between the <build> tags:

<plugin>
<groupId>org.codehaus.mojo.groovy</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>

We also need to create the source folder where Maven can find the Groovy source files. The path, relative to the projects root folder, should be src/test/groovy. This is where the plug-in will look for source files by default. Maven will then put the binary files in target/test-classes.

To run a specific test with Maven, use the normal command:

mvn test -Dtest=package.TestCase

Some basic language constructs

Before creating the first test case I should introduce some important language constructs. Writing Groovy don't need to be any different then writing Java, as a matter of fact it's possible to C&P valid Java code into a Groovy class. This would how ever not provide any benefits, so lets look at what makes Groovy so attractive.

Since Groovy is a dynamically typed language it's not necessary to specify a data type for a variable. Instead the keyword def can be used to define it.

def person = new Person()

This can offer less typing and for narrow scopes makes the code easier to read. Watch out for doing this when working with wider scope variables however since it makes the code less understandable.

Three major differences with Java are the string, list and map handling. Strings, lists (java.util.ArrayList) and maps (java.util.HashMap) are built in to the language so creating and working with them is very simple.

First something that will be noticed in the code examples below and may be confusing. Its the use of semicolons. In Groovy semicolons are optional and I have chosen not to use them.

Strings are defined as in Java, but with the option of using def. The real benefits is when referencing other variables within a string:

def person = new Person(name:”Tomas Malmsten”)
println “The new persons name is $person.name.”

 

The above statement would look like this in Java:

Person person = new Person(“Tomas Malmsten”);
System.out.println(“The new persons name is “ + person.getName() + “.”);

 

To create a new list all you need to do is:

def list = [1, 2, 3, 4]

What goes in between [ ] will build up the list, each item split with a comma. To sort the list just:

sort(list)

To access an item in the list:

println list[2]
//Prints 3

 

There are several other such shorthand commands, all listed in the reference summary (see link in Further reading),

Maps works in exactly the same manner:

def map = [“one”:1, “two”:2, “three”:3]

The key assignment is before the : and the value is after.

To access items in the map:

println map[“two”]
//Prints 2

 

There are two ways to create a set:

def set1 = [1, 2, 3, 4] as Set
def set2 = new HashSet()

 

The second species the specific set implementation rather then leaving it to Groovy.

Groovy supports properties in the language. This means that instead of creating a bean like in Java:

public class Person {
private String name;
private int age;
private double height;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
...
}

 

 

You write this:

class Person {
String name
int age
double height
}

 

Groovy then provides two constructors to all objects, the default constructor and a constructor that takes a map like structure with name/value pairs to populate the objects selected properties.

Person person = new Person(name:”Tomas Malmsten”, age:35)

This creates a new person with the name and age properties set without the need to create a constructor that takes the specific arguments.

person.height = 1.82

This sets the height property. And the same syntax is used to get a property:

println person.name

The last important thing that I will bring up here is method return statements. All methods in Groovy returns something, namely the last executed statement. This can be a bit confusing at times but is quite useful as well. Look at the following code:

def createNewPerson(name, age) {
new Person(name:name, age:age)
}

 

No return statement is needed since the last executed statement was a call to the constructor of Person. The return keyword can still be used to return values, especially if this needs to happen within a section of conditional logic or similar.

Creating your first Groovy test case

Now that the first introduction to Groovy is done it's time to look at how to create a basic unit test in Groovy. It's actually very simple since the base test class for Groovy, GroovyTestCase, extends JUnit's TestCase. So the code needed to create the first test with a test method is:

class Test extends GroovyTestCase {
void testSomething() {
assertTrue(true);
}
}

 

The accessor for tests methods is removed since the default scope for Groovy is public. The class will be compiled using the Groovy compiler when the Maven2 test target is invoked.

The same conventions are used in Groovy as in JUnit. To create a setUp or a tearDown use this code:

protected void setUp() {}
protected void tearDown() {}

 

Everything else with a Groovy test case is pretty much the same as with a JUnit test case.

How Groovy can help speed up test creation

I recently wrote a test case for our service/menu system The system assigns a set of services to a user. Some of the services has menu items. Two services are available to clients. One is a service resolver that resolves a users services dependent on the roles assigned to the user. The other builds a menu using the services. When I am going to test this I need to create a set of services and menu items that I can then inject into the objects that are used to provide the services. Below is a snippet of what it would look like in Java:


...
group = new MenuGroup();
group.setPosition(0);
group.setName("TestGroup");

topLevelItem1 = new TopLevelItem();
topLevelItem1.setPosition(1);
topLevelItem1.setParent(group);

subLevelItem1 = new SubLevelItem();
subLevelItem1.setPosition(2);
subLevelItem1.setParent(topLevelItem1);

subLevelItem2 = new SubLevelItem();
subLevelItem2.setPosition(3);
subLevelItem2.setParent(topLevelItem1);

Set<Integer> roles = new HashSet<Integer>(3);
roles.add(1);
roles.add(2);
roles.add(3);

parent1 = new ParentService();
parent1.setMenuItem(topLevelItem1);
parent1.setName("parent1");
parent1.setRoleIds(roles);
parent1.setUrl("parent1");

topLevelItem1.setService(parent1);
...

This is only a bit of the actual code. Now lets look at what it's like in Groovy:

...
group = new MenuGroup(position : 0, name : "TestGroup")

topLevelItem1 = new TopLevelItem(position : 1, parent : group)

subLevelItem1 = new SubLevelItem(position : 2, parent : topLevelItem1)
subLevelItem2 = new SubLevelItem(position : 3, parent : topLevelItem1)

def roles = [1,2,3] as Set

parent1 = new ParentService(menuItem : topLevelItem1,

name : "parent1", roleIds : roles, url : "parent1")

topLevelItem1.service = parent1
...

So 22 lines of Java code have been reduced to 8 lines of Groovy code. This will of cause reduce the time it takes to write said code as well. Not to mention how much easier the Groovy code communicates the intent.

Mocking with EasyMock

The above is fairly basic test writing and data building. When working with creating self testing code it's important to also be able to mock dependencies. I always use EasyMock to mock dependencies since it's easy to use. It is also maintained within the test cases which creates a conceptually clean design. Since it's a technology I am familiar with I have chosen to use it in Groovy as well.

It's easy to work with EasyMock in groovy although since static imports aren't supported yet it's a bit more convoluted then in Java.

In order to work with EasyMock we need to add the dependency to the pom.xml file like below:

<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>

 

I will now return to the tests from the example above where I created test code for the service/menu system we use. Since it's a web based system it has dependencies on the HttpServletRequest, HttpServletResponse and the HttpSession interfaces. In order to mock the three interfaces I first need to create three mock controls:

def requestControl = EasyMock.createControl()
def responseControl = EasyMock.createControl()
def sessionControl = EasyMock.createControl()

 

I then create the mocked implementations like this:

request = requestControl.createMock(HttpServletRequest.class)
session = sessionControl.createMock(HttpSession.class)
response = responseControl.createMock(HttpServletResponse.class)

 

Now I need to record what behaviour is expected of the mock objects:

EasyMock.expect(request.getSession(false)).andReturn(session)
EasyMock.expect(request.getSession(false)).andReturn(session)
EasyMock.expect(
session.getAttribute(“foo”)
).andReturn("")

 

Next is to replay the mock object:

EasyMock.replay(session)
EasyMock.replay(request)
EasyMock.replay(response)

 

Now I can use the objects to test against. When I have tested against them I can also verify that the expected calls were made to the interfaces by using the verify operation:

EasyMock.verify(session)
EasyMock.verify(request)
EasyMock.verify(response)

 

To understand more about how EasyMock works for both Java and Groovy there are good documentation at the EasyMock site at http://www.easymock.org. The instructions are easy to understand, especially if you follow the examples in detail first.

Further reading

The Groovy Reference Summary is a handy PDF document that is well worth printing and have on the desk when working with Groovy: http://docs.codehaus.org/download/attachments/2715/groovy-reference-card.pdf

The Groovy homepage is where Groovy lives and where you will find most documentation you need: http://groovy.codehaus.org/

The EasyMock website for all things to do with the framework: http://www.easymock.org.

P.S I have, after writing this article, found out that Groovy now supports static imports. This is great news and I am looking forward to using it!

References
Published at DZone with permission of its author, Tomas Malmsten. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Paul King replied on Thu, 2008/03/06 - 5:17pm

Nice article. You could consider upgrading to Groovy 1.5+ which supports static imports. You might also like to consider the built-in mocking capabilities. EasyMock is nice but the built-in support saves having to download an extra package.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.