Reputation: 1375
I'm trying to add unit tests to some legacy code that has a String class name passed to it and that creates an object implementing a particular handler interface using Class.newInstance(String className)
. I can control the class name I'm passing, I can get a pointer to the new handler object (via a getHandler()
call), and I would like to observe calls to it using Mockito.
My current solution is:
TestHandler
that implements the interface.getMock()
method.objectUnderTest.getHandler().getMock()
.This works, but feels a little inelegant, especially having to manually write all the pass-thru methods.
Is there a better solution?
Upvotes: 3
Views: 5418
Reputation: 1025
Create a public method where you will place the logic to fetch the newInstance of the class
ClassA objectClassA=createNewInstance(className);
likewise,and
public ClassA createInstance(String className){
return (ClassA) (Class.forName(className)).newInstance();
}
Now suppose we were creating an instance of classA inside of ClassB then in TestClass of B, we can simply mock this createInstance method
doReturn(mockClassA).when(mockClassB).createInstance(className);
Upvotes: 0
Reputation: 95634
Fundamentally, you're running into the same problems as trying to test a newly-created instance using new
; the Class.newInstance
(probably properly Class.forName(foo).newInstance()
) doesn't hurt you, but doesn't help you either.
As a side note, your TestHandler
sounds like a general purpose delegate implementation, which sounds pretty useful anyway (particularly if you ever need to write a Handler wrapper). If it is, you might want to promote it to be adjacent to your Handler in your production code tree.
Though I recognize that you mention legacy code, this becomes very easy if you are allowed to refactor to include a testing seam. (Ignoring reflective exceptions here for ease of explanation.)
public ReturnType yourMethodUnderTest(String className) {
return yourMethodUnderTest(Class.newInstance(className));
}
/** Package private for testing. */
public ReturnType yourMethodUnderTest(Handler handler) {
return yourMethodUnderTest(Class.newInstance(className));
}
You could also extract the object creation and replace it in your test:
/** Instance field, package-private to replace in tests. */
Function<String, Handler> instanceCreator =
( x -> (Handler) Class.forName(x).newInstance());
public ReturnType yourMethodUnderTest(String className) {
Handler handler = instanceCreator.apply(className);
// ...
}
You could even just extract it to a method and replace it in your test:
public ReturnType yourMethodUnderTest(String className) {
Handler handler = createHandler(className);
// ...
}
/** Package private for testing. */
Handler createHandler(String className) {
return Class.forName(className).newInstance();
}
@Test public void yourTest() {
// Manually replace createHandler. You could also use a Mockito spy here.
ObjectUnderTest objectUnderTest = new ObjectUnderTest() {
@Override Handler createHandler(String className) {
return mock(Handler.class);
}
}
// ...
}
Side note: Even though Mockito creates a named dynamic type, you almost certainly will not be able to hack it in and allow your code to create it by name. This is because the call to mock
registers the instance within Mockito's internal state.
// BAD: Unlikely to work
@Test public void yourTest() {
objectUnderTest.methodUnderTest(
mock(Handler.class).getClass().getName());
// ...
}
Upvotes: 1