Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Follow up on Writing unit tests using Groovy

DZone's Guide to

Follow up on Writing unit tests using Groovy

· Java Zone
Free Resource

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

Tomas Malmsten recently wrote on Writing unit tests using Groovy, one of the reasons he choose Groovy for testing is that you can be more expressive with it, reduce the amount of code you have to write while retaining the same testing behavior. He also shows how to setup Groovy with Maven2 to execute your tests. The point of this article is to show other options you have at your disposal to continue in the groovier testing path.

Building Testing Data

I'm a big fan of builders, so when I first saw Tomas' example I immediately thought that a builder was perhaps better suited to create the testing data. If you are wondering what builders are think of it as helper classes that let you write hierarchical structures without having the structure getting in the way. Let's look at the example proposed by Tomas, there is a top level object (MenuGroup) which contains a TopLevelItem, which in turn contains 2 SubLevelItems and 1 ParentService. You can create the data using the builder syntax as follows

def group = builder.menuGroup( position: 0, name: 'TestGroup' ){
topLevelItem( position: 1 ){
subLevelItem( position: 2 )
subLevelItem( position: 3 )
parentService( name: 'parent1', url: 'parent1',
roleIds: [1,2,3] as Set )
}
}

Much better, but I'm sure you are probably wondering "How am I supposed to write the builder?" There are several options, but for this particular case, building test data, there is an easy solution: configure an instance of ObjectGraphBuilder. This builder has been specially tailored to be extensible, as not every domain model follows the JavaBean conventions, which is the case of this test model. You will notice in Tomas' article that items have a "setParent" method and that the relationship between a TopLevelItem and ParentService is through a "setService" method, we need to map these relationships somehow. I'm assuming the following layout on the domain

abstract class Item { 
int position
Item parent
List<Item> children = new ArrayList<Item>()
}

interface Service { }

class MenuGroup extends Item {
String name
}

class TopLevelItem extends Item {
Service service
}

class SubLevelItem extends Item { }

class ParentService implements Service {
TopLevelItem menuItem
String name
Set<Integer> roleIds = new HashSet<Integer>()
String url
}

ObjectGraphBuilder comes with a set of strategies that help it find out how to instantiate and setup your model, the default ones assume that your model adheres to the JavaBean conventions, and even handles Collection properties. In this case all items must add themselves to their parent (I optionally added the children property to later navigate the model). Having a 'parent' property on the base class Item solves the problem of setting the parent, but the children property does not follow the JavaBeans convention (should have been items or subLevelItems to be more exact). Now ParentService is another matter altogether, as it declares a parent TopLevelItem property with menuItem as the name, so the default mapping will not work out of the box. This means we must change two strategies to properly wire up our domain model.

The first strategy we will update takes care of resolving the names of the parent/child relationship on each en, so we map the children property and parent/menuItem based on the arguments of each call

builder.relationNameResolver = [
resolveChildRelationName: { parentName, parent, childName, child ->
"children" // default to this value
},
resolveParentRelationName: { parentName, parent, childName, child ->
child instanceof Service ? "menuItem" : "parent"
}
] as ObjectGraphBuilder.RelationNameResolver

We fix the return of each call to the appropriate values, which can clearly be extended to use maps if the domain grows. Now we must update the second strategy, the one that takes care of actually wiring the parent and child together.

builder.childPropertySetter = { parent, child, parentName, propertyName ->
if( child instanceof Service ){
parent.service = child
} else if( propertyName == "children" ){
parent.children << child
}
}

That should do it, so now we are able to create the test data as in the first snippet. ObjectGraphBuilder also provides support for synthetic properties, properties that do not belong to your domain, and handle them accordingly, this is because it extends for the more general FactoryBuilderSupport base builder, which basically gives many things for free. We can add an id property to any node while building, thus creating a reference to that node inside the builder instance, allowing the following code to b tested

def group = builder.menuGroup( position: 0, name: 'TestGroup' ){
topLevelItem( position: 1 ){
subLevelItem( position: 2 )
subLevelItem( position: 3 )
parentService( name: 'parent1', url: 'parent1',
roleIds: [1,2,3] as Set, id: 'ps' )
}
}

assert group.children.size() == 1
def tli = group.children[0]
assert tli instanceof TopLevelItem
assert tli.children.size() == 2
assert tli.children.position == [2,3] // GPath expression
assert tli.service == builder.ps // ps is a variable in builder
assert tli.service.roleIds.size() == 3 // GPath expression

It may be the case for some domain models that id is a valid property so this setting can also be changed by registering a IdentifierResolver strategy.

When Tests Should Fail

As long as your tests walk the golden path everyone should be happy, but what happens when you actually need to test for exceptional conditions? the dreaded try/catch/check exception block quickly arises. Fortunately GroovyTestCase includes a handy solution for this scenario: shouldFail. This little helper works in two ways, you can pass a closure to it, if the code doesn't throw any exception then the test fails; if you want a finer detail on which exception should be triggered you can pass its class as the first parameter. Let's look at an example using the previous builder.

Following the configured strategies so far you may have noticed that the builder will use the node name, transform it somehow and use it as the class name to be instantiated, so if we write a node that doesn't belong to any of the domain classes we have in our model, the test should fail, right? After wrapping the previous snippets into a subclass of GroovyTestCase we can execute the following and get a green bar.

   void testBuildShouldFail(){
shouldFail {
def group = builder.menuGroup( position: 0, name: 'TestGroup' ){
topLevelItem( position: 1 ){
subLevelItem( position: 2 ){
thirdLevelItem( position: 3 )
}
}
}
}
}

The test code throws a ClassNotFoundException, wrapped in a RuntimeException, so the shouldFail block succeeds. What if you would like to be more specific and catch the ClassNotFoundException? you could pass that class as the first parameter to shouldFail but you will quickly see that it doesn't produce the desired result, as the real exception thrown is a RuntimeException, we must find a way to query for the cause of the top level exception, which is rather easy, we just need to add a new helper method to our testcase, like the following one

   private void shouldFailWithCause( Throwable cause, Closure closure ){
try {
closure()
fail("An expected exception was not thrown: $cause")
}catch( Throwable t ){
assert t?.cause?.class == cause.class
}
}

We are ready to test the failing build code and catch the correct exception

   void testBuildShouldFailWithCause(){
shouldFailWithCause( ClassNotFoundException ){
def group = builder.menuGroup( position: 0, name: 'TestGroup' ){
topLevelItem( position: 1 ){
subLevelItem( position: 2 ){
thirdLevelItem( position: 3 )
}
}
}
}
}

Conclusion

ObjectGraphBuilder is a good way to build test data, but clearly it has more potential than just test data, as a matter of fact there is a DomainBuilder in Grails that extends the concept for creating bootstrap data. GroovyTestCase has other goodies to offer that I hope you continue to explore, in the end using Groovy as test language will bring fun to a boring/repetitive (yet really important) task as many consider it, it will make you more productive at the very least.

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}