Mockito Tasty mocking framework for unit tests in Java
Mockito 是一个模拟框架,味道非常好。它使您可以使用干净简单的 API 编写漂亮的测试。Mockito 不会给您带来宿醉,因为测试非常易读,并且会产生干净的验证错误。
除了引入 mockito-core 外,还需要引入 mockito-junit-jupiter 扩展包。其中 mockito-inline 是对 Mockito 的增强,包括 mock final 类 static 方法。
1
2
3
4
5
6
|
// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation 'org.mockito:mockito-core:4.8.1'
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
testImplementation 'org.mockito:mockito-junit-jupiter:4.8.1'
// https://mvnrepository.com/artifact/org.mockito/mockito-inline
testImplementation 'org.mockito:mockito-inline:4.8.1'
|
如果使用 maven 为了单测能正常运行并被 mvn test
识别,还需要增加 maven-surefire-plugin。
在JUNIT5中,不再提供 @RunWith
注解,需要使用 @ExtendWith
注解。
为了让Mockito正常工作,需要在@ExtendWith注解中使用MockitoExtension.class
同时,@Before被@BeforeAll或者@BeforeEach替换,若使用@BeforeAll,需要写在静态方法上。
验证交互
1
2
3
4
5
6
7
8
9
10
11
12
|
import static org.mockito.Mockito.*;
// mock creation
List mockedList = mock(List.class);
// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();
// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();
|
stub(存根) method 调用
1
2
3
4
5
6
7
8
9
10
11
|
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
|
mock()
/@Mock
: 创建模拟
Answer
/MockSettings
可选指定它的行为方式
when()
/given()
指定模拟应该如何表现
- 如果提供的 answers 不符合您的需求,请自己编写一个扩展 Answer 接口
spy()
/@Spy
:部分模拟,调用真正的方法,但仍然可以验证和存根
@InjectMocks
: 使用 @Spy
或 @Mock
注解自动注入 mocks/spies 字段
verify()
: 检查使用given参数调用的方法
- 可以使用灵活的参数匹配,例如通过
any()
- 或捕获被
@Captor
调用的参数
- BDDMockito 尝试使用行为驱动开发(Behavior-Driven development)语法
切记
- 不要模拟你不拥有的类型
- 不要模拟值对象
- 不要嘲笑一切
- 用你的测试表达爱!
基于注解的初始化
除了使用 Mockito.mock()
方法 mock 对象,推荐使用注解来完成。
1
2
|
// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation 'org.mockito:mockito-core:4.5.1'
|
Mockito 3 后 MockitoAnnotations.openMocks()
方法启用注解。Mockito 2 中 MockitoAnnotations.initMock()
被标记为 deprecated。
1
2
3
4
5
6
7
8
9
10
11
|
private AutoCloseable closeable;
@BeforeEach
public void open() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void close() throws Exception {
closeable.close();
}
|
Mockito JUnit 5 Extension
1
2
3
4
|
dependencies {
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1'
}
|
@ExtendWith(MockitoExtension.class)
标记类后可以使用注解自动 mock 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionInjectMocksTest {
@Mock
private OrderRepository orderRepository;
@InjectMocks
private OrderService orderService;
@Test
void createOrderSetsTheCreationDate() {
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order order = new Order();
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
}
|
@Mock
@Mock:模拟出一个Mock对象,对象是空的,需要指明对象调用什么方法,传入什么参数时,返回什么值
@Mock
来创建和注入模拟实例
Mockito.mock()
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
27
|
public class ArticleManagerTest extends SampleBaseTestCase {
@Mock private ArticleCalculator calculator;
@Mock(name = "database") private ArticleDatabase dbMock;
@Mock(answer = RETURNS_MOCKS) private UserProvider userProvider;
@Mock(extraInterfaces = {Queue.class, Observer.class}) private ArticleMonitor articleMonitor;
@Mock(stubOnly = true) private Logger logger;
private ArticleManager manager;
@Before public void setup() {
manager = new ArticleManager(userProvider, database, calculator, articleMonitor, logger);
}
}
public class SampleBaseTestCase {
private AutoCloseable closeable;
@Before public void openMocks() {
closeable = MockitoAnnotations.openMocks(this);
}
@After public void releaseMocks() throws Exception {
closeable.close();
}
}
|
Answer<T>
1
2
3
4
5
6
7
8
9
10
11
|
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);
}
});
//Following prints "called with arguments: [foo]"
System.out.println(mock.someMethod("foo"));
|
MockSettings
1
2
3
4
5
6
7
8
9
10
11
|
//Creates mock with different default answer and name
Foo mock = mock(Foo.class, withSettings()
.defaultAnswer(RETURNS_SMART_NULLS)
.name("cool mockie")
);
//Creates mock with different default answer, descriptive name and extra interfaces
Foo mock = mock(Foo.class, withSettings()
.defaultAnswer(RETURNS_SMART_NULLS)
.name("cool mockie")
.extraInterfaces(Bar.class));
|
@Spy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Test
public void whenNotUseSpyAnnotation_thenCorrect() {
List<String> spyList = Mockito.spy(new ArrayList<String>());
spyList.add("one");
spyList.add("two");
Mockito.verify(spyList).add("one");
Mockito.verify(spyList).add("two");
assertEquals(2, spyList.size());
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class Test{
//Instance for spying is created by calling constructor explicitly:
@Spy Foo spyOnFoo = new Foo("argument");
//Instance for spying is created by mockito via reflection (only default constructors supported):
@Spy Bar spyOnBar;
private AutoCloseable closeable;
@Before
public void init() {
closeable = MockitoAnnotations.openMocks(this);
}
@After
public void release() throws Exception {
closeable.close();
}
...
}
|
@InjectMocks
@InjectMocks:依赖@Mock对象的类,也即是被测试的类。@Mock出的对象会被注入到@InjectMocks对象中
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
|
public class ArticleManagerTest extends SampleBaseTestCase {
@Mock private ArticleCalculator calculator;
@Mock(name = "database") private ArticleDatabase dbMock; // note the mock name attribute
@Spy private UserProvider userProvider = new ConsumerUserProvider();
@InjectMocks private ArticleManager manager;
@Test public void shouldDoSomething() {
manager.initiateArticle();
verify(database).addListener(any(ArticleListener.class));
}
}
public class SampleBaseTestCase {
private AutoCloseable closeable;
@Before public void openMocks() {
closeable = MockitoAnnotations.openMocks(this);
}
@After public void releaseMocks() throws Exception {
closeable.close();
}
}
|
verify()
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
27
28
29
30
31
32
33
34
35
36
|
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");
//verification using atLeast()/atMost()
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
//stubbing using anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
//following prints "element"
System.out.println(mockedList.get(999));
//you can also verify using argument matcher
verify(mockedList).get(anyInt());
|
@Captor
参数
1
2
3
4
5
6
7
8
9
10
|
@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {
List mockList = Mockito.mock(List.class);
ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);
mockList.add("one");
Mockito.verify(mockList).add(arg.capture());
assertEquals("one", arg.getValue());
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class Test{
@Captor ArgumentCaptor<AsyncCallback<Foo>> captor;
private AutoCloseable closeable;
@Before
public void open() {
closeable = MockitoAnnotations.openMocks(this);
}
@After
public void release() throws Exception {
closeable.close();
}
@Test public void shouldDoSomethingUseful() {
//...
verify(mock).doStuff(captor.capture());
assertEquals("foo", captor.getValue());
}
}
|
Behavior Driven Development
行为驱动开发(英语:Behavior-driven development,缩写BDD)是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。BDD最初是由Dan North在2003年命名,它包括验收测试和客户测试驱动等的极限编程的实践,作为对测试驱动开发的回应。在过去数年里,它得到了很大的发展
编写测试的行为驱动开发风格使用 //given
//when
//then
注释作为测试方法的基本部分。
BDDMockito 类引入了一个别名,以便您使用given(Object)方法存根方法调用。
1
2
|
public class BDDMockito
extends Mockito {}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import static org.mockito.BDDMockito.*;
Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);
public void shouldBuyBread() throws Exception {
//given
given(seller.askForBread()).willReturn(new Bread());
//when
Goods goods = shop.buyBread();
//then
assertThat(goods, containBread());
}
|
将 mock 注入 spy
1
2
3
4
5
6
7
8
9
10
11
12
13
|
MyDictionary(Map<String, String> wordMap) {
this.wordMap = wordMap;
}
@Mock
Map<String, String> wordMap;
MyDictionary spyDic;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
spyDic = Mockito.spy(new MyDictionary(wordMap));
}
|
1
2
3
4
5
6
7
8
9
10
|
public class MockitoAnnotationsUninitializedUnitTest {
@Mock
List<String> mockedList;
@Test(expected = NullPointerException.class)
public void whenMockitoAnnotationsUninitialized_thenNPEThrown() {
Mockito.when(mockedList.size()).thenReturn(1);
}
}
|
总结
Mockito 注解的注意事项
- Mockito 的注解最大限度地减少了重复的模拟创建代码。
- 它们使测试更具可读性。
- @InjectMocks 是注入 @Spy 和 @Mock 实例所必需的。