本文翻译自Using Mockito With JUnit 5。
在本教程中将学习在JUnit 5
中如何使用Mockito
框架,我们将以独立的方式学习测试框架,之后学习如何在JUnit 5
中使用Mockito
扩展。
本文是JUnit 5 教程的一部分。
相关视频
如果你喜欢通过视频学习,可以查看Youtube
中相关的学习视频。
手工初始化
在做其它事情之前,需要添加Mockito
相关的依赖:
1
2
3
|
dependencies {
testImplementation('org.mockito:mockito-core:3.12.4')
}
|
如果我们只是想创建一个要注入其它对象的mock,最简单的方式是调用Mockito.mock()
方法,该方法将要实例化的对象的类作为参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class MockitoManualTest {
private OrderRepository orderRepository;
private OrderService orderService;
@BeforeEach
void initService() {
orderRepository = mock(OrderRepository.class);
orderService = new OrderService(orderRepository);
}
@Test
void createOrderSetsTheCreationDate() {
Order order = new Order();
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
}
|
手工初始化在我们没有太多要mock的对象时是一个合法的解决方案。
优点:
缺点:
- 代码会变得很冗长
- 无法检测框架的使用情况以及检测不正常的打桩
基于注解初始化
Mockito.mock()
的一种声明式替代方案为给要mock的对象上添加@Mock
注解,同时需要调用一个特殊方法来初始化被注解的对象。
在Mockito 2
中有一个MockitoAnnotations.initMocks()
方法,该方法已经被废弃并在Mockito 3
中被MockitoAnnotations.openMocks()
替代,该方法返回一个AutoCloseable
对象可用于在测试之后关闭资源。
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
|
public class MockitoAnnotationTest {
@Mock
private OrderRepository orderRepository;
private AutoCloseable closeable;
private OrderService orderService;
@BeforeEach
void initService() {
closeable = MockitoAnnotations.openMocks(this);
orderService = new OrderService(orderRepository);
}
@AfterEach
void closeService() throws Exception {
closeable.close();
}
@Test
void createOrderSetsTheCreationDate() {
Order order = new Order();
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
}
|
MockitoAnnotations.openMocks(this)
方法的调用可告知Mockito
扫描测试类中任何包含有@Mock
注解的属性并将这些属性初始化为mock对象。
优点:
缺点:
Mock自动注入
同样可以通过给对应属性添加@InjectMocks
注解来自动注入mock对象。
当调用MockitoAnnotations.openMocks()
时,Mockito
会执行如下操作:
- 对有
@Mock
注解的属性创建mock对象
- 对有
@InjectMocks
的属性创建实例并尝试将前一个步骤中创建的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
27
28
|
public class MockitoInjectMocksTests {
@Mock
private OrderRepository orderRepository;
private AutoCloseable closeable;
@InjectMocks
private OrderService orderService;
@BeforeEach
void initService() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void closeService() throws Exception {
closeable.close();
}
@Test
void createOrderSetsTheCreationDate() {
Order order = new Order();
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
}
|
Mockito
将首先尝试通过构造器注入的方式注入mock对象,之后为setter注入、属性注入。
优点:
缺点:
注意
不推荐使用属性注入或setter注入,使用构造器注入时,我们可以百分百确定没有注入相关依赖之前无法实例化该类。
JUnit5 Mockito扩展
JUnit 5
中同样有一个Mockito
扩展让实例化更简单。
要使用该扩展,首先需要添加对应的依赖。
1
2
3
|
dependencies {
testImplementation('org.mockito:mockito-junit-jupiter:3.12.4')
}
|
现在可使用该扩展并避免对MockitoAnnotations.openMocks()
的方法调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@ExtendWith(MockitoExtension.class)
public class MockitoExtensionTest {
@Mock
private OrderRepository orderRepository;
private OrderService orderService;
@BeforeEach
void initService() {
orderService = new OrderService(orderRepository);
}
@Test
void createOrderSetsTheCreationDate() {
Order order = new Order();
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
}
|
同样可以在MockitoExtension
中使用@InjectMocks
注解来进一步的简化配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@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对象注入到方法参数中。
1
2
3
4
5
6
7
8
9
10
11
|
@Test
void createOrderSetsTheCreationDate(@Mock OrderRepository orderRepository) {
OrderService orderService = new OrderService(orderRepository);
when(orderRepository.save(any(Order.class))).then(returnsFirstArg());
Order order = new Order();
Order savedOrder = orderService.create(order);
assertNotNull(savedOrder.getCreationDate());
}
|
将mock对象注入方法参数中既适用于生命周期方法也适用于测试方法本身。
优点:
- 不需要调用
MockitoAnnotations.openMocks()
方法
- 能检测框架使用情况以及不正确的打桩
- 容易创建mock对象
- 容易阅读
缺点:
- 需要一个额外的依赖
org.mockito:mockito-junit-jupiter
总结
在JUnit 5
中有3种不同的方式使用Mockito
,前2种方式可独立于框架单独使用,而第3种方式需使用JUnit 5
中的Mockito
扩展。
Mock对象可通过一下方式创建和初始化:
- 通过调用
Mockito.mock()
手工创建
- 添加
@Mock
注解并通过调用MockitoAnnotations.openMocks()
方法来初始化
- 添加
@Mock
注解并在测试中使用MockitoExtension
扩展
本文的示例代码能在GitHub中找到。