Mockito

Dan North, the originator of Behavior-Driven Development wrote this back in 2008:
“We decided during the main conference that we should use JUnit 4 and Mockito because we think they are the future of TDD and mocking in Java”


说明
本文章中的示例使用的开发环境为JDK1.8!
Mockito的版本信息如下

1
2
3
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>

文章中的示例部分来自Mockito Dzone Reference Card

Warning:文章中的示例只是为了展示Mockito的语法和特性,在实际的测试编码中,我们可能不会使用示例的用法(测试逻辑)!
官方原文:

Warning: Note that the examples in this Refcardwere created to demonstrate behaviors of Mockito in a specific context. Of course, when writing the test for your codebase, there is no need to ensure that mocks are stubbed correctly.

鄙人在Github上修改了部分示例,并且写了一些其他的示例(实际测试代码中用法)!传送门


简介

Mockito是一个模拟测试框架。在一个被测试的对象(功能)A中,它通常需要与其他的对象(功能)B进行一些交互,我们把A称作被测试对象(tested object),把B称作协作者(collaborators)。那么在测试环境中,这些协作者都需要被创建,以便被测试对象可以使用它们。为了使测试代码简化以及满足上下文执行环境,我们通常使用测试替身(test double)来代替这些协作者,测试替身看上去和原始的协作者一样,但是却不依赖其他对象,而且可以执行预期行为,记录他们的交互行为(interactions,可以理解成方法调用)


核心元素

TDD : Test-Driven Development

BDD : Behavior-Driven Development

Mock : 模拟对象,可以理解成Mockito框架帮我们自动生成的数据。通过mock产生的对象有以下的能力:1.可以通过编程产生预期行为;2.在对象的生命周期内可以校验它的交互行为(方法调用);

原文参考:Mock - an object with the ability to a) have a programmed expected behavior, and b) verify the interactions occurring in its lifetime (this object is usually created with the help of mocking framework)

Stub : 存根?桩?可以理解成通过硬编码的方式预期定义行为(方法),将会产生特定的结果,屏蔽原本行为(方法)的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**举例,伪代码描述*/

//define a method
public string sayABC() {
return "ABC";
}

//stubbing
define when call sayABC the return "CBA"

//act
sayABC(); //call then we got "CBA" not "ABC"

原文参考:Stub - an object with hardcoded behavior suitable for a given test (or a group of tests)

Spy : mock的代理对象,当方法被stub的时候,调用stub的定义行为(方法实现被忽略);当方法没有被stub时,调用真实对象的行为(调用真实方法的逻辑实现)。此对象一般不由mock生成,而是自己编码new,再通过spy包装成mock

原文参考:Spy - a mock created as a proxy to an existing real object; some methods can be stubbed, while the un-stubbed ones are forwarded to the covered object

Test Doubles:测试替身,包括5个类型stub,mock,spy,fake,dummy。


地心历险(待研究)

揭示底层的原理


实战

1. 概述

编写测试用例,一般分为三个阶段:

Section name Responsibility
arrange (given) SUT and mocks initialization and configuration
act (when) An operation which is a subject to testing;preferably only one operation on an SUT
assert (then) The assertion and verification phase

arrange-act-assert模式对应的语法为:when(~).thenXXX(~)
given-when-then模式(对应BDD形式)对应的语法为:given(~).willXXX(~)
官方大部分DEMO使用given-when-then模式,而且也推荐使用这种模式

官方原文: given-when-then comments make intentions of tests clearer.

2. 简例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.testng.annotations.Test;
import static org.mockito.Mockito.*;
import static org.testng.Assert.assertEquals;

public class SimpleStubbingTest {
public static final int TEST_NUMBER_OF_LEAFS = 5;

@Test
public void shouldReturnGivenValue() {
// arrange
Flower flowerMock = mock(Flower.class);
when(flowerMock.getNumberOfLeafs()).thenReturn(TEST_NUMBER_OF_LEAFS);

// act
int numberOfLeafs = flowerMock.getNumberOfLeafs();

// assert
assertEquals(numberOfLeafs, TEST_NUMBER_OF_LEAFS);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import static org.mockito.Mockito.*;
import static org.mockito.BDDMockito.*;
import static org.junit.Assert.*;

public class SimpleStubbingTest {
public static final int TEST_NUMBER_OF_LEAFS = 5;

@Test
public void shouldReturnGivenValueUsingBDDSemantics() {
//given
Flower flowerMock = mock(Flower.class);
given(flowerMock.getNumberOfLeafs()).willReturn(TEST_NUMBER_OF_LEAFS);

//when
int numberOfLeafs = flowerMock.getNumberOfLeafs();

//then
assertEquals(numberOfLeafs, TEST_NUMBER_OF_LEAFS);
}
}

3. 参数匹配

(Argument Matching)
Mockito默认使用equals()来匹配参数。通常我们需要一个宽范围的参数匹配,Mockito的org.mockito.Matchers类中提供了一套内置的匹配器(Matcher)。
代码示例:

1
2
3
4
5
given(plantSearcherMock.smellyMethod(anyInt(), contains("asparag"), eq("red"))).willReturn(true);

//given(plantSearcherMock.smellyMethod(anyInt(), contains("asparag"), "red")).willReturn(true);

//incorrect - would throw an exception

如果有一个参数使用了匹配器,则所有的参数必须都使用匹配器,否则将会抛出异常!示例代码中注释掉的部分会抛出异常!

自定义的参数匹配器需要继承org.mockito.ArgumentMatcher抽象类,并且实现matches方法。然后调用argThat(org.hamcrest.Matcher matcher)方法。
代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
given(schedulerMock.getNumberOfPlantsScheduledOnDate(
argThat(haveHourFieldEqualTo(7)))).willReturn(1);

//with the util method to create a matcher
private ArgumentMatcher haveHourFieldEqualTo(final int hour) {
return new ArgumentMatcher() {

@Override
public boolean matches(Object argument) {
return ((Date) argument).getHours() == hour;
}
};

}

4. 调用Void方法

Stubbing Void Methods
void Methods 应该使用 willXXX…given 或者 doXXX…when.来进行stubbing
代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test(expectedExceptions = WaterException.class)
public void shouldStubVoidMethod() {

WaterSource waterSourceMock = mock(WaterSource.class);
doThrow(WaterException.class).when(waterSourceMock).doSelfCheck();

//the same with BDD semantics
//willThrow(WaterException.class).given(waterSourceMock).doSelfCheck();

waterSourceMock.doSelfCheck();

//exception expected

}

5. 自定义返回器

Stubbing With a Custom Answer
极少的情况会使用自己的处理逻辑来指定预期行为的结果(也就是given…willReturn(Custom Answer)中的Answer).Mockito还是提供了org.mockito.stubbing.Answer<Object>的接口来实现这个功能,你只需实现该接口中的answer方法。

Warning:如果需要使用Custom Answer,可能预示着被测代码太复杂,需要重构!
官方原文:

Warning: The need to use a custom answer may indicate that tested code is too complicated and should be re-factored.

6. 检验行为

(Verifying Behavior)
在一个mock对象的生命周期内,它会记住本身所有的操作。在被测系统(SUT)中,这些操作应该可以被轻易校验。Mockito中可以使用Mockito.verify(T mock)这个基础形式来进行校验
代码示例:

1
2
3
4
5
WaterSourcewaterSourceMock = mock(WaterSource.class);

waterSourceMock.doSelfCheck();

verify(waterSourceMock).doSelfCheck(); //默认校验一次调用

Mockito提供了一些有意义的校验模式,你也可以创建自定义校验模式

1
2
3
4
5
verify(waterSourceMock,never()).doSelfCheck();

verify(waterSourceMock,times(2)).getWaterPressure();

verify(waterSourceMock,atLeast(1)).getWaterTemperature();

7. 校验调用顺序

(Verifying Call Order)
Mockito可以让你校验调用的顺序,你可以创建一组mocks,然后在组内校验所有的调用顺序。
代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void shouldVerifyInOrderThroughDifferentMocks(){

WaterSourcewaterSource1=mock(WaterSource.class);
WaterSourcewaterSource2=mock(WaterSource.class);

waterSource1.doSelfCheck();
waterSource2.getWaterPressure();
waterSource1.getWaterTemperature();

InOrderinOrder=inOrder(waterSource1,waterSource2);
inOrder.verify(waterSource1).doSelfCheck();
inOrder.verify(waterSource2).getWaterPressure();
inOrder.verify(waterSource1).getWaterTemperature();

}

8. 真实调用

Spying on Real Objects
在Mockito中,你可以使用真实的对象来代替mock,从而使部分方法可以stubbed。通常我们不需要使用spy一个真实的对象,这可能是代码异味(code smell)的信号。但在一些特殊的情况下(比如使用了遗留代码,或者IOC容器),纯mock对象可能不能进行测试。
代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void shouldStubMethodAndCallRealNotStubbedMethod() {

Flower realFlower = new Flower();
realFlower.setNumberOfLeafs(ORIGINAL_NUMBER_OF_LEAFS);
FlowerflowerSpy=spy(realFlower);
willDoNothing().given(flowerSpy).setNumberOfLeafs(anyInt());

flowerSpy.setNumberOfLeafs(NEW_NUMBER_OF_LEAFS); //stubbed,and should do nothing

verify(flowerSpy).setNumberOfLeafs(NEW_NUMBER_OF_LEAFS);
assertEquals(flowerSpy.getNumberOfLeafs(),ORIGINAL_NUMBER_OF_LEAFS); //value was not changed

}

当使用spy时,必须使用willXXX…given/ doXXX…的形式来stubbing,它可以在stub期间防止真实方法方法被调用

Warning:当使用spy时,Mockito创建了真实对象的一份拷贝,因此所有的交互行为应该被传递到被创建的spy对象上
官方原文:

Warning: While spying, Mockito creates a copy of a real object, and therefore all interactions should be passed using the created spy.

9. 注解

Annotations
Mockito提供三个注解来简化用静态方法创建mock对象的工作 – @Mock, @Spy, @Captor;注解@InjectMocks可以简化mock和spy对象的注入,它可以通过构造器注入,setter方法注入,field赋值注入。

使用注解的功能要调用MockitoAnnotations.initMocks(testClass)(通常在@Before的方法中调用),或者使用MockitoJUnit4Runner来作为junit runner

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//with constructor: PlantWaterer(WaterSource waterSource,
// WateringScheduler wateringScheduler) {...}

public class MockInjectingTest {

@Mock
private WaterSource waterSourceMock;

@Spy
private WateringScheduler wateringSchedulerSpy;

@InjectMocks
private PlantWaterer plantWaterer;

@BeforeMethod
public void init() {
MockitoAnnotations.initMocks(this);
}

@Test
public void shouldInjectMocks() {
assertNotNull(plantWaterer.getWaterSource());
assertNotNull(plantWaterer.getWateringScheduler());
}

}
Annotation Responsibility
@Mock Creates a mock of a given type
@Spy Creates a spy of a given object
@Captor Creates an argument captor of a given type
@InjectMocks Creates an object of a given type and injects mocks and spies existing in a test

10. 修改默认返回值

(Changing the Mock Default Return Value)

Mockito使我们可以选择生成mock对象的默认值

Default Answer Description
RETURNS_DEFAULTS Returns a default “empty” value (e.g., null, 0, false, empty collection) - used by default
RETURNS_SMART_NULLS Creates a spy of a given object
RETURNS_MOCKS Returns a default “empty” value, but a mock instead of null
RETURNS_DEEP_STUBS Allows for a simple deep stubbing (e.g., Given(ourMock.getObject().getValue()).willReturn(s))
CALLS_REAL_METHODS Call a real method of spied object

mock(clazz, Mockito.${Default Answer})

Tips

依照本人的理解,在真正编写测试代码时,我们应该分清当前单元测试的目的,以及它所依赖的方法调用。然后在given阶段,mock各种依赖的对象,并且stub各种依赖对象的预期行为;when阶段进行测试目的的执行,也就是测试对象真实的调用行为;then阶段进行verify和assert。


局限性

引用官方说明:
Limitations

  • Mockito has a few limitations worth remembering. They are generally technical restrictions, but Mockito authors believe using hacks to work around them would encourage people to write poorly testable code. Mockito cannot :
    • mock final classes
    • mock enums
    • mock final methods
    • mock static methods
    • mock private methods
    • mock hashCode() and equals()

参考资料

鄙人编写的代码示例(部分来自官网示例,适量修改,并添加额外的示例)
Mockito官网
Mockito On Github
Mockito Dzone Reference Card
Introduction to mockito


Mockito
https://oabern.github.io/posts/201612072734635843/
作者
OABern
发布于
2016年12月7日
许可协议