【发布时间】:2019-05-22 06:22:42
【问题描述】:
我正在对 Spring Boot 微服务进行单元测试。我想使用 Mockito 在某个进程中调用方法时抛出异常,以便我可以提高单元测试覆盖率。问题是这个方法(我们称之为doB())被一个只存在于方法的本地范围(B)中的对象调用,该对象是由一个静态最终工厂对象(A)创建的。
我想做一些类似的事情:
doThrow(new RuntimeException()).when(mockedB).doB(anyString());
这样我就可以断言fooResult 返回null,从而通过覆盖捕获异常情况来提高测试覆盖率。
但是,是否有可能以有用的方式创建mockedB?如果有,怎么做?
我尝试了mock() 和spy() 的各种组合,但收效甚微。我是使用 Mockito 的新手,但我很确定问题的症结在于,如果我只是模拟 Foo,那么我将看不到 A 和 B 在里面做事,但试图模拟或间谍A 或B 也不起作用,因为它们与Foo 内部创建的A 或B 不同。 A 是 final 和 static 在这里也可能对我没有任何好处。
例如,除了最基本的功能外,我几乎删除了所有内容。我正在测试的类是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。这不起作用,也不会抛出异常。
我觉得尝试这个是不好的做法。更好的方法可能是更改方法,以便我可以传入我自己的 A 对象,以便我可以观察它。但是,假设我不能更改任何源代码。这甚至可能吗?
【问题讨论】:
-
考虑将构造函数添加到
Foo,以便可以将A的Spring bean 注入Foo。Foo.localA可能不需要是静态的,但它仍然是最终的是个好主意。这通过模拟A并使用构造函数将其放入Foo来简化单元测试。模拟的A可以设置为抛出异常,而无需对B做任何事情。
标签: java spring-boot junit mockito powermockito