我正在对Spring Boot微服务进行单元测试。我想使用Mockito在某个过程中调用方法时引发异常,以便提高单元测试的覆盖率。问题在于此方法(我们称其为doB()
)仅由该方法的局部作用域(B
)中存在的对象调用,该对象由静态最终工厂对象(A
)创建)。
我想做以下事情:
doThrow(new RuntimeException()).when(mockedB).doB(anyString());
这样我可以断言fooResult
返回null
,因此可以通过覆盖catch异常情况来提高测试覆盖率。
但是,是否有可能以一种有用的方式创建一个mockedB
?如果可以,怎么办?
我尝试了mock()
和spy()
的各种组合,但收效甚微。我是使用Mockito的新手,但是我很确定问题的症结在于,如果我只是模拟Foo,那么在做事时我将看不到A
和B
,但尝试模拟或监视A
或B
也不起作用,因为它们与在{{1}内部创建的A
或B
不同}。 Foo
最终还是静态的在这里对我也没有任何帮助。
作为示例,我除去了几乎所有基本功能,但仅删除了基本功能。我正在测试的类是A
,它在内部使用Foo
和A
。
这里是B
:
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;
}
}
:
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();
}
}
:
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);
}
}
FooResult
最后,这是测试:
public class FooResult {
private String fooString;
public FooResult(String str) {
this.fooString = str;
}
public String getFooString() {
return fooString;
}
}
如前所述,我希望模拟在调用@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);
}
}
时触发,从而导致doB()
返回doB()
。这不起作用,并且不会引发异常。
我觉得尝试这样做是一种不好的做法。更好的方法可能是更改方法,以便我可以传入我自己的null
对象,以便可以观察它。但是,我们只能说我不能更改任何源代码。这有可能吗?
答案 0 :(得分:0)
实际上,我在键入时找到了一个解决方案,所以我想继续分享一下。是的!
贷方到this post寻求答案:
由于模拟无法处理最终结果,因此我们最终要做的是侵入字段本身的根。当我们使用Field操作(反射)时,我们正在寻找类/对象内的特定变量。 Java找到它后,我们将获得它的“修饰符”,该修饰符告诉变量它具有诸如final,static,private,public等的限制/规则。我们找到正确的变量,然后告诉代码可访问的变量允许我们更改这些修饰符。一旦更改了根目录下的“访问权限”以允许我们对其进行操作,我们便会切换到它的“最终”部分。然后,我们可以更改该值并将其设置为我们需要的任何值。
使用他们的setFinalStatic
函数,我可以成功模拟A
并引发RuntimeException
。
以下是工作测试以及帮助程序:
//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);
}
当然,如果有人有更好更好的方法,那么我很乐意接受这个答案。