{ by david linsin }

April 19, 2009

Using Reflection in Unit Tests

A couple of days ago I watched a presentation on Neal Ford's book "The Productive Programmer". He talked about 10 ways to improve your code. One thing, which I found quite innovative, was leveraging Java Reflection to test non-accessible code.

With non-accessible code, I mean private, package-private or protected methods and constructors of your domain or service classes. If you are a test addict like me, you've probably made a method public or protected every now and then, even though it shouldn't be, simply so that you could access it from your test code. Using Reflection instead, is a simple and easy solution, I have never given a thought before.

I came up with a little sample, which you can download or check out on github. Take a look at the following class:

public class Contract {
private static final Double RATE = 0.3;
private final Long contractId;
private BigDecimal quote;
private Customer owner;

public Contract(Long argContractId) {
contractId = argContractId;
quote = BigDecimal.ZERO;
}

/** package */void setOwner(Customer argOwner) {
owner = argOwner;
quote = calculateQuote(argOwner.getBirthday());
}

private BigDecimal calculateQuote(DateMidnight argBirtday) {
Years ageInYears = Years.yearsBetween(argBirtday, new DateMidnight());
return new BigDecimal(RATE).multiply(new BigDecimal(ageInYears.getYears()));
}

// other stuff omitted
}


In order to test the calculateQuote method in isolation, I would have probably introduced a QuoteCalculator class. It would be in charge of the business logic and used by the Contract class. If you need the logic at multiple places in your code, that's a great solution. If you only need it in Contract, it's quite tedious and I would have probably made calculateQuote accessible from the test class. However, I used Reflection instead to solve the problem and it works quite nicely:

@Test
public void test_Calculate_Quote() throws Exception {
Contract classUnderTest = new Contract(1L);
Method calcMethod = getMethodOfClass(Contract.class, "calculateQuote");
BigDecimal quote = (BigDecimal) calcMethod.invoke(classUnderTest, new DateMidnight(1982, 4, 27));

assertEquals(new BigDecimal("7.79"), quote.setScale(2, RoundingMode.FLOOR));
}

private Method getMethodOfClass(Class argClass, String argMethodName) {
Method[] methods = argClass.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(argMethodName)) {
method.setAccessible(true);
return method;
}
}
throw new NoSuchMethodError("couldn't find " + argMethodName + " on class " + argClass);
}


As you can see the method.setAccessible(true); is doing the trick to enable access from the test to the domain object. Instead of the actual instance of Contract, we use the Method instance to invoke the private call. Everything else is plain test code, as you know it.

Of course, you could argue to make the calculateQuote method package-private or protected and put the test class into the same package. In fact Bill Venners wrote about that in one of is articles, suggesting that testing private methods is bad practice anyways. I tend to agree and I guess the example above is an exception. However, using Reflection is not necessarily limited to testing private methods. You might need access to an object to manipulate it's internal state, in order to test it thoroughly and the best tool for that job is probably Reflection.

I think it's a neat idea to use Reflection in unit tests. Used with caution, it can be really helpful and is definitely better than breaking encapsulation for testing.

6 comments:

same said...

Hi Dave,

a few weeks ago we had a discussion upon that exact problem in our company and I got some bashing for the proposal to use PowerMock in such cases.

I have the impression that PowerMock does exactly what you are doing by using reflection (and a bit more).

The key argument of one of our senior developers was that he would rather set the visibility of a method to package protected than to have to use another testing tool. I don't really agree with that reasoning.

From my perspective it's much cleaner to keep a private method private and go for the reflection approach.

There is a video on PowerMock from 0redev 2008 if you don't know it yet.

PS: sorry, for deleting my post. I just wanted to add the link correctly. Maybe I should have used the preview feature. ;o)

David Linsin said...

Samuel > I have the impression that PowerMock does exactly what you are doing by using reflection (and a bit more).It does!! Thanks for the link and I guess I have the topic for my next blog post!

Samuel > The key argument of one of our senior developers was that he would rather set the visibility of a method to package protected than to have to use another testing tool. I don't really agree with that reasoning.

From my perspective it's much cleaner to keep a private method private and go for the reflection approach.
You are absolutely right! Don't break encapsulation for testability! If no other testing tools are allowed, use reflection as shown in my sample.

Thx for the comment!

Unknown said...

You are both wrong and should listen to the senior developer, who is right:
- do not change visibility of methods and members for testability.
- do not test internal's of a class.
- using setAccessible to true usually stops all the reasoning about a class state. The object no longer knows it's own state.
- do test the contract of a class, it's behavior.

If you need sth like this, your design of the classes is probably wrong. You got multiple behaviors in a class, not programming to interfaces, the construction of the beans is not correct, etc.

David Linsin said...

klaasjan > do not change visibility of methods and members for testability.

That's what both of us are arguing for!

klaasjan > do not test internal's of a class....do test the contract of a class, it's behavior.

Whitebox vs. Blackbox testing?

klaasjan > using setAccessible to true usually stops all the reasoning about a class state. The object no longer knows it's own state.

That can easily be contained within a unit test!

Martin Faartoft said...

I agree with klaasjan, don't write a test for every method, write tests for the behaviour of the unit under test. Assuming you write sufficient tests for edgecases etc., all private methods should be completely covered. If they're not, chances are they don't really belong in the unit.

David Linsin said...

Martin > Assuming you write sufficient tests for edgecases etc., all private methods should be completely covered.I do agree to write unit tests for edge cases. However, sometimes it's not only about testing private methods and thus behavior. It's about enabling testing and therefore you need to get your object into a certain state. It is rare that you need to do it, but I've encountered it a couple of times and thus having a tool like reflection comes in handy to help out.


com_channels

  • mail(dlinsin@gmail.com)
  • jabber(dlinsin@gmail.com)
  • skype(dlinsin)

recent_postings

loading...