Monday, September 24, 2007

Overriding final Java classes in unit tests using Groovy

As with a lot of legacy codebases, the code I work with on a daily basis is sometimes extremely hard to unit test. There are times where the scope and constraints of your changes don't allow you to refactor the code until it is testable. In fact, if there is low test coverage in the first place, refactoring the code is an extremely dangerous endeavor.

There are some known techniques for testing software that wasn't designed to be tested. In Java, you can place the test classes in the same package as the production classes and then call package and protected methods on the system under test without breaking the compiler's visibility rules. Or you can use reflection to examine and invoke methods on a system under test that are not visible, such as private methods. One method I like to use is to create a test specific subclass by subclassing the system under test and gaining visibility to state and behavior not available through its public interface. None of these strategies are pretty, and they all break some object oriented principles somewhere. Life is full of compromises. I may be willing to make some that you are not.

My problem is that I'm not allowed to subclass my production classes because they are all marked final (sealed in C#). Final methods may not be subclassed. This is done because of a rigid (blind?) adherence to Joshua Bloch's Effective Java rule of "Design for inheritance or prohibit it". Let's not consider right now whether this is actually a good thing. Instead, let's look at how you can break this rule by using Groovy to create a test specific subclass of a final class.

Start with a simple production class that is final... no subclass can be made:


public final class SystemUnderTest {
public String foo() {
return "Executing within SystemUnderTest...";
}
}

The following Groovy test case shows how to "subclass" this final class to alter the behavior of the final SystemUnderTest object. A GroovyTestCase is a lot like a JUnit TestCase, and I'll explain what is happening below the example.

import groovy.mock.interceptor.*

class OverridingFinalTest extends GroovyTestCase {
void test_Overriding_Final() {
// Mock out our class under test
def mock = new MockFor(SystemUnderTest)

// Demand that the getString method gets called
mock.demand.foo {
return "Executing within Groovy..."
}

// Use our mock for this block
mock.use {
def mocked = new SystemUnderTest()
assert mocked.foo() == "Executing within Groovy..."
}

def real = new SystemUnderTest()
assert real.foo() == "Executing within SystemUnderTest..."
}
}


The "new MockFor" statement is creating a mock object for the SystemUnderTest class. The mock.demand.foo block is redefining the behavior for the foo() method for that object. The mock.use block tells Groovy to use the mock object as a replacement for the actual SystemUnderTest within the use block. Any call to SystemUnderTest within the mock.use block will be filtered through our custom definition of the mock. So creating a new SystemUnderTest in the use block is actually creating an instance of our mock object, which delegates to an instance of the SystemUnderTest class... in this way our redefined foo() will execute the Groovy code when called, but any other method on the SystemUnderTest will be dispatched to the real SystemUnderTest.

As the test shows, invoking new SystemUnderTest outside the use block will result in not using the mock... and foo() method calls are dispatched to the real object, not filtered through our mock.

My real intent when starting this project was to create an object factory in Groovy that could return to me subclasses of final classes. But the Groovy mock never implements or extends the class it is mocking, it is just a proxy for that class. In the bytecode, the MockFor is not a SystemUnderTest object, so any attempt to return that object back to Java and use it as a SystemUnderTest will fail. What does this mean? You still can't mock out final classes in Java test cases, but it is entirely possible in Groovy test cases. Now if we could just get the architects to allow us to check in Groovy test cases...

Thank you to the Groovy Users of Minnesota and Jesse for helping me with the example.

1 comment:

Anonymous said...

I've found this post very interesting. I had no idea that you can so easily get rid of final modifier in unit tests in groovy. Lately I'm working a lot on the issue of testing hard-to-test-code (static calls, constructor calls, final classes) and your post gave me another idea on how to deal with it.

thanks !
Tomek Kaczanowski
http://kaczanowscy.pl/tomek