Scott McIntyre
Scott McIntyre

Reputation: 1375

How to mock an object created via Class.newInstance(className)?

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:

  1. Create a new test class TestHandler that implements the interface.
  2. Have that test class contain a Mockito mock object that also implements the interface.
  3. Manually pass through all the interface methods to the mock object.
  4. Make the mock object accessible via a getMock() method.
  5. Observe the object by making verify() calls to 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

Answers (2)

Vivek Singh
Vivek Singh

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

Jeff Bowman
Jeff Bowman

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

Related Questions