I am unit testing a Spring Boot micro-service. I want to use Mockito to throw an exception when a method is called in a certain process so that I can improve unit test coverage. The problem is that this method (let's call it doB()
) is called by an object that only exists in the method's local scope (B
), created by a static final factory object (A
).
I want to do something along the lines of:
doThrow(new RuntimeException()).when(mockedB).doB(anyString());
so that I can assert that fooResult
returns null
and therefore improve test coverage by covering the catch exception case.
But, is it even possible to create a mockedB
in a useful way? If so, how?
I have tried various combinations of mock()
and spy()
, admittedly with little success. I'm new to using Mockito, but I'm pretty sure the crux of the problem is because if I just mock Foo then I won't be able to see the A
and B
inside doing things, but trying to mock or spy A
or B
doesn't work either since they aren't the same as the A
or B
created inside of Foo
. A
being final and static probably isn't doing me any favors here either.
I have removed pretty much all but the bare essential functionality as an example. The class I am testing is Foo
which uses A
and B
internally.
Here is Foo
:
public class Foo {
private static final A localA = new A();
public FooResult doFoo(String fooString) {
try {
B localB = localA.createB();
return localB.doB(fooString);
} catch (RuntimeException e) {
//exception handling here
}
return null;
}
}
And this is A
:
public class A {
//unimportant internal details
private Object property;
public A() {
this(null);
}
public A(Object property) {
this.property = property;
}
public B createB() {
//assume for sake of example that this constructor
//is not easily accessible to classes other than A
return new B();
}
}
And now B
:
public class B {
public FooResult doB(String str) throws RuntimeException {
//lots of processing, yada yada...
//assume this exception is difficult to trigger just
//from input due to details out of our control
return new FooResult(str);
}
}
Here is FooResult
public class FooResult {
private String fooString;
public FooResult(String str) {
this.fooString = str;
}
public String getFooString() {
return fooString;
}
}
Finally, here is the test:
@RunWith(PowerMockRunner.class)
public class FooTest {
@InjectMocks
Foo foo;
@Test
public void testDoFoo() {
String fooString = "Hello Foo";
FooResult fooResult = foo.doFoo(fooString);
assertEquals(fooResult.getFooString(), fooString);
//works fine, nothing special here
}
@Test
@PrepareForTest({Foo.class, A.class, B.class})
public void testDoFooException() throws Exception {
//magic goes here???
A mockedA = PowerMockito.mock(A.class);
B mockedB = PowerMockito.mock(B.class);
PowerMockito.when(mockedA.createB()).thenReturn(mockedB);
PowerMockito.doThrow(new RuntimeException()).when(mockedB).doB(Mockito.anyString());
FooResult fooResult = foo.doFoo("Hello Foo");
//expect doFoo() to fail and return null
assertNull(fooResult);
}
}
As I said earlier, I expect the mock to trigger when doB()
is called, causing doB()
to return null
. This doesn't work and the exception is not thrown.
I have a feeling this is bad practice to be trying this. A better way would probably be to change the method so that I could pass in my own A
object instead so that I could observe it. But, let's just say that I can't change any source code. Is this even possible?
I actually just found a solution as I was typing this up, so I thought I would go ahead and share it. Yay!
Credit goes to this post for the answer:
Since Mocking cannot handle final, instead what we end up doing is hacking into the root of the field itself. When we use the Field manipulations (reflection), we are looking for the specific variable inside of a class/object. Once Java finds it we get the "modifiers" of it, which tell the variable what restrictions/rules it has like final, static, private, public, etc. We find the right variable, and then tell the code that it is accessible which allows us to change these modifiers. Once we have changed the "access" at the root to allow us to manipulate it, we are toggling off the "final" part of it. We then can change the value and set it to whatever we need.
Using their setFinalStatic
function I am able to successfully mock A
and cause the RuntimeException
to be thrown.
Here is the working test along with the helper:
//make fields accessible for testing
private static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
// remove final modifier from field
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
@Test
@PrepareForTest({A.class})
public void testDoFooException() {
A mockedA = PowerMockito.mock(A.class);
B mockedB = PowerMockito.mock(B.class);
try {
setFinalStatic(Foo.class.getDeclaredField("localA"), mockedA);
} catch (Exception e) {
fail("setFinalStatic threw exception: " + e);
}
Mockito.when(mockedA.createB()).thenReturn(mockedB);
PowerMockito.doThrow(new RuntimeException()).when(mockedB).doB(Mockito.anyString());
FooResult fooResult = foo.doFoo("Hello Foo");
assertNull(fooResult);
}
Of course, if anyone has a better and less hacky way to do this I would be happy to accept that answer instead.