Saturday, April 5, 2008

Testing Java from Groovy? 2 Misconceptions

The Groovy language has a great story around unit testing... the built in MockFor and StubFor objects demo so well, dynamic typing lets you pull some clever run time tricks, and the Java interoperability means you can test your Java code from Groovy. From time to time I also hear that Groovy unit tests on Java code are an easy way to get Groovy into your organization. Given that unit testing is easier in Groovy (it is), and Groovy interops great with Java (it does), it should be a no-brainer to convince the authorities that dropping in groovy-all.jar is a good thing, right?

Well, when it came time for me to sell this idea to my work, my argument fell flat. The problem was that my two biggest reasons for advocating Groovy testing turned out to be false. Let's look at each in turn.

Groovy lets you mock out static method calls
Legacy codebases are often plagued by static dependencies in the form of static method calls. But in Groovy you're allowed to mock these out! Awesome, you can actually intercept a static method call to MyService.makeDatabaseCall() and provide a mock response. This is huge for me. Consider an example of a static service (written and compiled in Java!) providing file sizes for String based filenames:

import java.io.File;

public class JavaService {
static long getFileSize(String path) {
File file = new File(path);
return file.length();
}
}
And let's say the class you want to test makes a static call to this method...
class GroovyClient {
def filename;

public long getFileSize() {
return JavaService.getFileSize(filename);
}
}
We have a Groovy class we want to test making a static call to a Java object. Untestable in Java, easily mocked in Groovy:
def client = new GroovyClient(filename: "shortFile.txt")   

def mock = new MockFor(JavaService)
mock.demand.getFileSize("shortFile.txt") { -1 }
mock.use {
assert -1 == client.getFileSize()
}
This is huge, it means static dependencies no longer results in untestable code! Eh, not quite. This works because the object making the static call is written in Groovy. If the object making the call is written in Java (as my production code is) then the static call can't be intercepted. So if you make the "GroovyClient" above into a "JavaClient" the test will fail. Grrr.

This is no secret; nobody told me this would work. In fact, someone probably told me it would not work and I just wasn't listening. That's how misconceptions arise, I suppose.

So... it turns out that mocking out statics isn't a good argument for testing in Groovy, but it is a good argument for writing production code in Groovy (admittedly a harder sell).

Groovy lets you mock out the new operator
If you stop to think about it, you'll realize I'm just repeating myself here. But just in case you don't want to think...

Remember how our JavaService performed a "new File()" operation? Well, you can mock that out too! This is a great way to get inside your objects being tested and break some of those static dependencies:
def mock = new MockFor(File)         

mock.demand.length() { longFileSize }
mock.use {
assert longFileSize == GroovyService.getFileSize("shortFile.txt")
}
Sweet, Groovy lets you mock out new calls. Holla! And it doesn't work if the new is being performed by Java. What is new, anyway? In some languages new is just a class method, the same way that any of your other static methods are class methods. In Java, it's a little more special than that, but you can still think of it as a static method call. All the reasons you can't mock out a static call in Java apply to trying to mock out new.

Again, Groovy is a great choice for writing unit tests, but works best when the production code is also Groovy.

So what's it good for?
Despite discussing two reasons not to write unit tests for Java code in Groovy, I still do. These two posts hit the nail on the head: complex object creation is much simpler in Groovy than Java. In Groovy, my setUp code is shorter and simpler. Properties on the constructor makes object creation easy. Using a map to implement an interface makes stubs easy. Closures, collect() and inject() make working with large test data sets trivial. Builders simplify object graph creation. When testing Java from Groovy, it's all about how nice your setUp code becomes. That's the most convincing argument to me!

And what about you? Why do you test Java from Groovy? Or why not?

(And the big unanswered question of the post is why doesn't this work. I could look at the documentation and search the mailing list to find an answer, but it's Saturday morning, the weather is beautiful, and I'm going out to run some parkour. Yes, that's me in the blue track suit). Enjoy Spring, I say.

3 comments:

Jeff Olson said...

This doesn't help your efforts to use Groovy to test Java code (which I think is a noble goal), but I recently discovered the JMockit testing framework, which allows you to easily mock out static method calls, static initializers, etc. This has proved very helpful in testing a legacy Java application which has a lot of that type of coding (it was initially written circa 1999-2000).

paulk_asert said...

You can also use JMockit with Groovy:
http://groovy.codehaus.org/Using+JMockit+with+Groovy

Unknown said...

Hi. Thanks for posting this. I've suspected this for a while and confirmed it a couple of months ago. Since Groovy keeps getting better day by day, i've also discovered a lightweight way of unit testing code, without having to resort to mockFor and stubFor (which are great)....and it will only work for groovy unit tests testing groovy production code: Here's the link if you haven't experimented with it already:

http://groovy.codehaus.org/ExpandoMetaClass

This simple metaclass functionality cuts down your code to one or two lines, as opposed to the potential multiple lines using the aforementioned unit testing objects for groovy.