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

时间:2018-12-21 20:37:19

标签: java spring-boot junit mockito powermockito

我正在对Spring Boot微服务进行单元测试。我想使用Mockito在某个过程中调用方法时引发异常,以便提高单元测试的覆盖率。问题在于此方法(我们称其为doB())仅由该方法的局部作用域(B)中存在的对象调用,该对象由静态最终工厂对象(A)创建)。

我想做以下事情:

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

这样我可以断言fooResult返回null,因此可以通过覆盖catch异常情况来提高测试覆盖率。

但是,是否有可能以一种有用的方式创建一个mockedB?如果可以,怎么办?

我尝试了mock()spy()的各种组合,但收效甚微。我是使用Mockito的新手,但是我很确定问题的症结在于,如果我只是模拟Foo,那么在做事时我将看不到AB ,但尝试模拟或监视AB也不起作用,因为它们与在{{1}内部创建的AB不同}。 Foo最终还是静态的在这里对我也没有任何帮助。

作为示例,我除去了几乎所有基本功能,但仅删除了基本功能。我正在测试的类是A,它在内部使用FooA

这里是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对象,以便可以观察它。但是,我们只能说我不能更改任何源代码。这有可能吗?

1 个答案:

答案 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);
}

当然,如果有人有更好更好的方法,那么我很乐意接受这个答案。