How does one unit test an abstract class?
A while back I came upon a piece of untested legacy code, and I decided I was going to write some tests for it. The code consisted of an abstract base class with about 10 actual classes inheriting from it. I’m not sure if this is ‘the way’ to do it, but it worked for me and it was better than anything else I could think of. If there are better ways to tackle this problem then I’d like to know. But for now, this is what I came up with.
For the sake of keeping things simple, let’s give the classes some simple names like Car for the base class, and Volvo, Jaguar, Opel etc. for the implementing classes. All I’m using is plain JEE, no DI frameworks like Spring. Also note that I don’t care about properties of an actual car or how to best arrange them. Obviously the analogy between my real classes and a car is flawed, but that’s what you get when you simplify things. I could just as easily have picked FileParser or Woozle instead of Car – it doesn’t matter for the problem I’m trying to solve. With that out of the way, let’s first look at a few of the methods in this Car class.
public abstract class Car { private static final String DEFAULT_PREFIX = "P: "; @Inject private MyService someInjectedProperty; protected abstract ComplexObject getStuff(); protected abstract String getMeAValue(); protected String getMeAPrefixedValue() { return DEFAULT_PREFIX + getMeAValue(); } protected String getMeAnotherValue() { return "Another value."; } protected String getMeYetAnotherValue() { return someInjectedProperty.giveMeAValue(); } }
So this is rather simple really, but it does cover some interesting points:
- There’s a completely abstract method (well, two actually)
- There’s a regular method that calls an abstract method
- There’s a regular method that does nothing special
- There’s a regular method that gets its data from somewhere else
So now to get to the point of how to test this class. Do you want to test this class? Yes, because it contains methods that need to be tested, and I don’t want to do it each time I write a test class for a new implementation of Car. So what are my options here? And what works?
- I could write a TestCar class in my test packages and make it extend Car
- But I would have to write dummy implementations for all the abstract methods.
- I could write an anonymous inner class in my PeugotTest class
- But I really can’t be bothered writing 10 of these, and they still require me to write dummy implementations of all the abstract methods.
- I could try to use a mocking framework to take care of things for me
- But do I want a framework just to write a simple test?
Option one (and two)
The first option seems like a somewhat decent solution, but it does have me write dummy code, and it doesn’t feel right to write tests against that. Who knows what the actual implementation of getMeAValue() is going to be like, so what’s the point of writing a test when my test method would just return an empty string or something like that? And when the product – and the amount of abstract methods in this class – do I really want to keep writing more of these silly dummy methods? I remember seeing a class like this once, a DummyResultSet if I recall correctly. It implemented roughly 50 methods with return “”; and return null; – now that’s completely ridiculous isn’t it?
Option three (and four.. sort of)
So what about a mocking framework then? Lucky for me the company uses JUnit and Mockito by default, so I don’t have to worry about introducing frameworks just for this. A downside for me is that if Mockito doesn’t offer me a solution, I’m done for. No extra frameworks allowed. But I’m lucky again, there is a way to get this to work. And if you’re working on a project of any size, you should probably already be using some sort of mocking framework, so introducing dependencies isn’t really an excuse either. I believe there are also ways to accomplish the same using JMockit, but obviously I didn’t try.
But how does this work then? Initially you would just go about writing a test like you normally do:
public class CarTest { @Mock private MyService mockSomeInjectedProperty; @InjectMocks private Car car; @Before public void setup() { car = Mockito.mock(Car.class, Mockito.CALLS_REAL_METHODS); // or if you're using version 1.10.12 or higher you can also use a Spy, like: // car = Mockito.spy(Car.class); MockitoAnnotations.initMocks(this); } @Test public void getMeAPrefixedValue() { when(car.getMeAValue()).thenReturn("ABC"); assertEquals("P: ABC", car.getMeAPrefixedValue()); } }
That looks alright. But when this is ran.. uh oh.. framework meltdown! Turns out the when(..) doesn’t work on abstract methods. But there is a simple fix for this: change the when(..) line with this:
doReturn("ABC").when(car).getMeAValue();
That will ensure your code will keep on working. Methods such as doNothing, doReturn and doThrows actually doWork with abstract methods, so even though the when(..) construction seems more natural with a method that returns something, this is the way to go. Note that you’re effectively still writing dummy implementations of abstract methods, but there is a little upside to using this method over the TestCar solution. Namely that you’re only writing simple pieces of code for the methods you are actually calling. And no boilerplate. Since getStuff() is never invoked in the Car class, I do not have to write any dummy implementation for it.
A final solution is similar to the one before, and I think it should depend on your situation which of them makes most sense. In the example above you’re basically telling Mockito to make its own implementation of Car, but to invoke real methods on it instead of treating it as a mock object completely. Depending on the amount of abstract methods, it may be more interesting to you to turn this thing around. Instead of defaulting to real methods, why not tell Mockito to call a real method only when you want it to.
public class CarTest { @Mock private Object mockSomeInjectedProperty; @InjectMocks private Car car; @Before public void setup() { car = Mockito.mock(Car.class); when(car.doStuff()).thenCallRealMethod(); } @Test public void getMeAPrefixedValue() { when(car.getMeAValue()).thenReturn("ABC"); assertEquals("P: ABC", car.getMeAPrefixedValue()); } }
This works and keeps the code clean, or as least as clean as it’s going to get. Some things to note though are that under the hood Mockito and other frameworks will make their own implementation class of your abstract class, so that part of option one doesn’t go away. You just don’t have to do it yourself anymore.
Another thing that might be worth looking into is moving the abstract methods to some other class if that makes any sense. Preferably a non-abstract one that you can just test as a whole, without writing stubs, mocks etc. of methods in the class you’re trying to test. To use the Car analogy, I might want to move stuff to a Wheels class, or CarElectronics class if I can. And maybe those do not have to be abstract.
On injection used in this post
One last thing. The only reason I didn’t use constructor injection in my example code is to save some space. I would normally prefer that over using initMocks() whenever I can for a few reasons:
- If I added an @Inject-ed CarService to Car, I might miss it and its usage in my tests. If I use constructor injection, my test would no longer compile, informing me I need to fix and possibly extend it.
- Constructors will get big if a class has many injected dependencies. This looks terrible, but is wonderful really. The mess is a direct indication that you need to change your code. Your class is doing too much, split it up. Or it’s doing nothing besides delegating stuff elsewhere, but this isn’t good design either.
Sadly @WebService annotated methods require a no-arg constructor, so it’s not always possible to use constructor injection. Sometimes you just have to write @Inject private ABC abc; or put it on a setter.