JUnit4 Tests in Groovy

Posted on March 21, 2008 by Scott Leberknight

On my current (Java-based) project we now write all new unit tests in Groovy, though there are a lot of legacy Java tests still out there that we haven't had time or felt the need to convert to Groovy-based tests. There are also several base test classes, also written in Java, that provide some nice set up and tear down code for certain classes of test. I happened to be testing a web controller and realized I needed (or rather, wanted) to make use of the existing AbstractViewControllerTest base class, which is written in Java, uses JUnit4 annotations, and provides, among other things, code that creates and sets up a simulated authenticated user for the controller along with a bunch of other stuff like mock requests, responses, session, helper methods, etc. So, the problem was that I needed to extend AbstractViewControllerTest and not GroovyTestCase, and initially got all bent out of shape about having to choose to write the test in Java and extend the base test or write it in Groovy extending GroovyTestCase and having to redo the stuff already provided by the base class.

Thankfully about that time Steve reminded me nicely that you can mix-and-match Groovy and JUnit4 annotations just fine since Groovy has now sported annotation support for a while now (since ~1.1 I think). So I can simply write the test in Groovy, extend the base test class, and use JUnit4 annotations to get exactly what I want. What's the point? Just remember that when writing tests in Groovy there is nothing that says you must extend GroovyTestCase and that you can use JUnit4 in Groovy tests. So for example you could have a Groovy test like this:

class MyTest extends TestBaseClass {
    @Before
    void doSetup() { ... }
	
    @Test
    void testSomething() { ... }
	
    @Test(expected = IllegalArgumentException)
    void testBadThing() { ... }
}

This way you can extend some other class if you choose, and get all the normal JUnit4 stuff. Since you are not extending GroovyTestCase one thing that is missing is shouldFail() so instead you use the "expected" parameter in your @Test annotation when you want to verify a specific type of exception was thrown.

Wackiness With String, GString, and Equality in Groovy

Posted on March 20, 2008 by Scott Leberknight

We ran into this one today while writing a Groovy unit test. Check out the following GroovyShell session:

groovy:000> test = []
===> []
groovy:000> (1..10).each { test << "Item $it" }
===> 1..10
groovy:000> test                               
===> [Item 1, Item 2, Item 3, Item 4, Item 5, Item 6, Item 7, Item 8, Item 9, Item 10]
groovy:000> test.contains("Item 1")
===> false

Um...what? We started by adding items to a list, e.g. "Item 1", "Item 2", and so on. Then we simply ask whether the list contains an item we know (or think) is there. But no, it prints false! How can test possibly not contain "Item 1?" First, what's going on here? Look at this second example:

groovy:000> test = []
===> []
groovy:000> (1..10).each { test << "Item $it" }
===> 1..10
groovy:000> test
===> [Item 1, Item 2, Item 3, Item 4, Item 5, Item 6, Item 7, Item 8, Item 9, Item 10]
groovy:000> println test[0].class 
class org.codehaus.groovy.runtime.GStringImpl
===> null

Ah ha! The items in the list aren't strings. So what? Look at this code:

groovy:000> a = "Item 2"
===> Item 2
groovy:000> a.class
===> class java.lang.String
groovy:000> n = 2
===> 2
groovy:000> b = "Item $n"
===> Item 2
groovy:000> b.class
===> class org.codehaus.groovy.runtime.GStringImpl
groovy:000> a == b
===> true
groovy:000> b == a
===> true
groovy:000> a.equals(b)
===> false
groovy:000> b.equals(a)
===> false

a is a normal Java String, whereas b is a Groovy GString. Even more interesting (or perhaps confusing), a == comparison yields true, but a comparison using equals() is false! So back in the original example where we checked if the 'test' list contained 'Item 1', the contains() method (which comes from Java not Groovy) uses equals() to compare the values and responds that in fact, no, the list does not contain the String "Item 1." Without getting too much more detailed, how to fix this? Look at this example:

groovy:000> test = []
===> []
groovy:000> (1..10).each { test << "Item $it".toString() }
===> 1..10
groovy:000> test
===> [Item 1, Item 2, Item 3, Item 4, Item 5, Item 6, Item 7, Item 8, Item 9, Item 10]
groovy:000> test.contains('Item 1')
===> true

Now it returns true, since in the above example we explicitly forced evaluation of the GStrings in the list by calling toString(). The Groovy web site explains about GString lazy evaluation stating that "GString uses lazy evaluation so its not until the toString() method is invoked that the GString is evaluated."

Ok, so we now understand why the observed behavior happens. The second question is whether or not this is something likely to confuse the You-Know-What out of developers and whether it should behave differently. To me, this could easily lead to some subtle and difficult to find bugs and I think it should work like a developer expects without requiring an understanding of whether GStrings are lazily-evaluated or not. Lazy evaluation always seems to come at a price, for example in ORM frameworks it can easily result in the "n + 1 selects" problem.

For comparison, let's take a look at similar Ruby example in an irb session:

>> test = []
=> []
>> (1..10).each { |n| test << "Item #{n}" }
=> 1..10
>> test
=> ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10"]
>> test.include? "Item 1"
=> true

Ruby "does the right thing" from just looking at the code. Ruby 1, Groovy 0 on this one. I don't actually know whether Ruby strings evaluate the expressions immediately or not, and should not need to care. Code that looks simple should not require understanding the implementation strategy of a particular feature. Otherwise it is broken in my opinion.

Dumping the Maven Groovy Plugin

Posted on March 19, 2008 by Scott Leberknight

On my current project we are using Maven2 (yeah, yeah I know) which I become less and less happy with over time. That's not the point of this blog though. The point is that we have been using the Groovy Maven Plugin for a few months now to compile tests written in Groovy. The project is a Java project but several months ago we started writing new tests entirely in Groovy. This has worked nicely for a while, until today in one test I wanted to use a static import, which Groovy 1.5.x supports just fine. Unfortunately it seems the Groovy Maven Plugin uses a much earlier version of Groovy which doesn't support static imports. After a lot of fiddling around trying to get the plugin to work, we simply gave up and went with using the Maven Ant plugin to compile the Groovy code instead. It actually works and now we know for sure that we are using Groovy version 1.5.4.

Java Versus Ruby/Rails Books Revisited...It Was Only A Matter Of Time

Posted on March 18, 2008 by Scott Leberknight

Remember when the big joke was to compare the book stacks of a typical Java web developer with that of a Ruby/Rails developer?

Java Versus Ruby Book Stacks

I suppose it was only a matter of time, because now maybe the Ruby/Rails stack looks more like this:

Ruby and Rails Books circa 2008

By the way, I like and use Ruby/Rails and use Java among other languages like Python and Groovy so I am not comparing the actual technologies, just the available reading material. The real trend is that since the original Java vs. Ruby books stack images first appeared, Ruby/Rails have gained a lot of traction and people are naturally writing a lot more material to satisfy the demand. I should also note that shawn believes that you still only need the original two Pragmatic Programmer books Programming Ruby and Agile Web Development with Rails. Regardless, there are a lot more options available now and I've heard people say books like The Ruby Way and Rails Recipes are very good books as well. Ah the paradox of choice.

Image credits: Antonio Cangiano for the Ruby/Rails recommended books image, and garbett.org for the Java vs. Ruby book stack image.