Mockito 详解(三)插桩

Posted by liangfei on 2017-07-08

每次插桩(Stubbing)都会产生一个 Invocation,本篇从 Invocation 着手,重点分析插桩的原理。

Invocation 之间的关系如下所示:

+----------+              +----------+
| StubInfo |--stubbedAt-->| Location |
+----------+              +----------+
     /|\
      |
 implements
      |  
      |  
+--------------+                            +---------------------+
| StubInfoImpl |-----has a----------------->| DescribedInvocation |
+--------------+                            +---------------------+

+---------------------+
| DescribedInvocation |
+---------------------+
     /|\         /|\  \ 
      |           |     \        +------------------+
      |           |       \      | InvocationOnMock | 
      |           |         \    +------------------+
      |           |           \       / 
      |           |           extends
      |        extends           \ /
      |           |        +------------+
 implements       |        | Invocation |
      |           |        +------------+
      |           |                  /|\
      |     +---------------------+   |
      |     | MatchableInvocation |   |
      |     +---------------------+   |
      |             /|\               |
      |              |                |
      |         implements           has
      |              |                |
      |              |                |
   +-------------------+              |
   | InvocationMatcher |--------------+
   +-------------------+
              |
              |                         +-----------------+
              +------ has list of ----->| ArgumentMatcher |
                                        +-----------------+

MatchableInvocationInvocation 的区别如下:

mock.foo();   // <- invocation
verify(mock).bar(); // <- matchable invocation

MockHandler

Invocation 通过 MockHandler 进行处理:

public interface MockHandler extends Serializable {
Object handle(Invocation invocation) throws Throwable;
}

Mockito 提供的的 MockHandler 实现了如下功能:

  1. 记录 Mock 对象上的方法调用以进行后续验证
  2. 当 Mock 对象被插桩之后捕获 stubbing 信息
  3. 返回 invocation 的插桩值

Invocation 表示在 Mock 方法上的一次调用,它包含两部分信息 DescribedInvocation & InvocationOnMock。

A method call on a mock object. Contains all information and state needed for the Mockito framework to operate.

DescribedInvocation

DescribedInvocation 只是用于描述 Invocation

public interface DescribedInvocation {
String toString();
Location getLocation();
}

其中 Location 用于描述 Invocation 的位置:

public interface Location {
String toString();
}

InvocationOnMock

InvocationOnMock 用于表示 InvocationMock 之间的数据联系。

public interface InvocationOnMock extends Serializable {
Object getMock();
Method getMethod();
Object[] getArguments();
<T> T getArgument(int index);
Object callRealMethod() throws Throwable;
}

Invocation

Invocation 除此之外,还包含其他功能:

public interface Invocation extends InvocationOnMock, DescribedInvocation {
boolean isVerified();
int getSequenceNumber();
Location getLocation();
Object[] getRawArguments();
Class<?> getRawReturnType();
void markVerified();
StubInfo stubInfo();
void markStubbed(StubInfo stubInfo);
boolean isIgnoredForVerification();
void ignoreForVerification();
}

StubInfo 用于描述 stubbing:

public interface StubInfo {
Location stubbedAt();
}

MatchableInvocation

MatchableInvocation 包含 Invocation 实例和 ArgumentMatcher 列表。它用于验证的过程(verification process)。

public interface MatchableInvocation extends DescribedInvocation {
Invocation getInvocation();
List<ArgumentMatcher> getMatchers();
boolean matches(Invocation candidate);
boolean hasSimilarMethod(Invocation candidate);
boolean hasSameMethod(Invocation candidate);
void captureArgumentsFrom(Invocation invocation);
}

InvocationContainer 关系图

+---------------------+                           +------------+
| InvocationContainer |--- clear / get list of--->| Invocation |
+---------------------+                           +------------+

Stubbing 关系图

   +-----------------+
   | OngoingStubbing |
   +-----------------+
           /|\
            |
        implements
            |
            |
   +-----------------+
   |   BaseStubbing  |<-----------------+
   +-----------------+                  | 
           /|\                          |
            |                           |
         extends                     extends 
            |                           |
            |                           |
+----------------------+    +---------------------+
| ConsecutiveStubbing  |    | OngoingStubbingImpl |
+----------------------+    +---------------------+

Stubbing

Stubbing 表示一次插桩,它的形式是 when(x).then(y),用于指定 mock 的行为。

Stubbing 的示例代码

when(mock.foo()).thenReturn(true);

可以通过如下代码获取 mock 对象的所有 stubbing:

Mockito.mockingDetails(mock).getStubbings();

Stubbing 的接口规范如下:

public interface Stubbing {
Invocation getInvocation();
boolean wasUsed();
}
  • getInvocation() 返回被插桩的方法调用,例如,mock.foo() 就是一个 Invocation
  • wasUsed() 用于表示 stubbing 是否被使用,如果 mock.foo() 没有被调用,那么 wasUsed() 返回 false,说明存在未被使用的 stubbing,为了保持 clarity of tests,最好删除未被使用的 stubbing 代码。

Stubber

Stubber 是一个插装器。

当我们用 doThrow()|doAnswer()|doNothing()|doReturn() 的形式进行插桩时,可以通过 Stubber 来选择 mock 对象的方法:

示例一

doThrow(new RuntimeException()).when(mockedList).clear();

示例二

doThrow(new RuntimeException("one")).doThrow(new RuntimeException("two")).when(mock).someVoidMethod();

Stubber 接口定义如下:

public interface Stubber {
<T> T when(T mock);
Stubber doThrow(Throwable... toBeThrown);
Stubber doThrow(Class<? extends Throwable> toBeThrown);
Stubber doThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... nextToBeThrown);
Stubber doAnswer(Answer answer);
Stubber doNothing();
Stubber doReturn(Object toBeReturned);
Stubber doReturn(Object toBeReturned, Object... nextToBeReturned);
Stubber doCallRealMethod();
}

可以看出,doXXX 方法的返回值是一个 Stubberwhen 方法对 mock 对象进行处理之后再返回这个对象。

OngoingStubbing

public interface OngoingStubbing<T> {
OngoingStubbing<T> thenReturn(T value);
OngoingStubbing<T> thenReturn(T value, T... values);
OngoingStubbing<T> thenThrow(Throwable... throwables);
OngoingStubbing<T> thenThrow(Class<? extends Throwable> throwableType);
OngoingStubbing<T> thenThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... nextToBeThrown);
OngoingStubbing<T> thenCallRealMethod();
OngoingStubbing<T> thenAnswer(Answer<?> answer);
OngoingStubbing<T> then(Answer<?> answer);
<M> M getMock();
}

调用 Mockito#when 返回一个 OngoingStubbing,通过 OngoingStubbing#thenXxx 可以改变 mock 对象的行为,从而产生一个 stubbing。

示例代码

when(mock.someMethod(anyString())).thenReturn(10);

Answer

不管是 Stubber 还是 OngoingStubbing,除了标准返回之外,都提供了自定义返回值的方法:

  • Stubber doAnswer(Answer answer);
  • OngoingStubbing<T> then(Answer<?> answer);

Answer 的接口定义如下:

public interface Answer<T> {
T answer(InvocationOnMock invocation) throws Throwable;
}

如下代码利用 Answer 改变了 mock 方法的行为:

when(mock.someMethod(anyString())).thenAnswer(
new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + Arrays.toString(args);
}
});

那么 mock.someMethod("foo") 的返回值就变成了 called with arguments: [foo]

Answer 不接受参数,只有返回值,Mockito 还提供了其他 5 个 Answer,分别接受不同个数的参数,然后返回一个值。

  • Answer1
  • Answer2
  • Answer3
  • Answer4
  • Answer5

只接受参数,没有返回值的 Answer 包括:

  • VoidAnswer1
  • VoidAnswer2
  • VoidAnswer3
  • VoidAnswer4
  • VoidAnswer5

ValidableAnswer 用到再分析


欢迎扫码关注 老梁写代码 微信公众号