使用 (Power)Mockito 跟踪本地对象状态和中断流程?
Posted
技术标签:
【中文标题】使用 (Power)Mockito 跟踪本地对象状态和中断流程?【英文标题】:Track local object state and interrupt flow with (Power)Mockito? 【发布时间】: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
做任何事情。
【参考方案1】:
实际上,我在打字时刚刚找到了一个解决方案,所以我想我会继续分享它。耶!
答案转给this post:
由于 Mocking 无法处理 final,因此我们最终要做的是侵入字段本身的根。当我们使用字段操作(反射)时,我们正在寻找类/对象内部的特定变量。一旦 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);
当然,如果有人有更好、更简单的方法来做到这一点,我很乐意接受这个答案。
【讨论】:
以上是关于使用 (Power)Mockito 跟踪本地对象状态和中断流程?的主要内容,如果未能解决你的问题,请参考以下文章