使用(Power)Mockito跟踪本地对象状态和中断流



我正在对SpringBoot微服务进行单元测试。我想使用Mockito在某个进程中调用方法时抛出异常,这样我就可以提高单元测试的覆盖率。问题是,这个方法(我们称之为doB())是由一个只存在于该方法的本地作用域(B)中的对象调用的,该对象由一个静态的最终工厂对象(A)创建。

我想做一些类似的事情

doThrow(new RuntimeException()).when(mockedB).doB(anyString());

以便我可以断言fooResult返回null,从而通过覆盖catch异常情况来提高测试覆盖率。

但是,有可能以一种有用的方式创建mockedB吗?如果是,如何?

我尝试过mock()spy()的各种组合,无可否认,收效甚微。我是使用Mockito的新手,但我很确定问题的关键是,如果我只是模拟Foo,那么我将无法看到里面的AB在做事情,但试图模拟或监视AB也不起作用,因为它们与Foo内部创建的AB不同。A是最终的和静态的可能对我也没有任何好处。

作为一个例子,我删除了几乎所有的基本功能。我正在测试的类是Foo,它在内部使用AB

这里是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);
}

}

正如我前面所说,我希望mock在调用doB()时触发,从而使doB()返回null。这不起作用,也不会引发异常。

我有一种感觉,尝试这种做法是不好的。更好的方法可能是更改方法,这样我就可以传入自己的A对象,这样我就能观察到它。但是,我们只能说我不能更改任何源代码。这可能吗?

实际上,我在打字时刚刚找到了一个解决方案,所以我想我会继续分享它。耶!

这篇文章的答案值得称赞:

由于Mocking无法处理final,因此我们最终要做的是侵入字段本身的根。当我们使用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);
}

当然,如果有人有更好的、不那么粗鲁的方法来做这件事,我很乐意接受这个答案。

相关内容

  • 没有找到相关文章

最新更新