Tips:在使用时一定要注意版本适配性问题
一、Mockito
1.1 Mock的使用
Mock 的中文译为仿制的,模拟的,虚假的。对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去。
Mock 测试就是在测试过程中,对于某些 不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)或者不容易获取 比较复杂 的对象(如 JDBC 中的 ResultSet对象),用一个 虚拟 的对象(Mock 对象)来创建,以便测试方法。
@Mock
注解用于创建模拟对象,而 @InjectMocks
注解用于注入依赖的模拟对象或真实对象到测试对象中。通常情况下,@Mock
和 @InjectMocks
会配合使用,在单元测试中提供模拟对象和被测试对象之间的依赖关系。
public class BookService {
private BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public Book findBook(String id) {
return bookRepository.findById(id);
}
}
@ExtendWith(MockitoExtension.class)
class BookServiceTest {
@Mock
private BookRepository bookRepository;
@InjectMocks
private BookService bookService;
@Test
void findBook() {
Book book = new Book("1", "Title");
when(bookRepository.findById("1")).thenReturn(book);
bookService.findBook("1");
verify(bookRepository, times(1)).findById("1");
}
}
创建了一个模拟的 BookRepository
,并且使用了 @InjectMocks
将它注入到了 BookService
的实例中。
1.2 @Mock @Spy @InjectMocks
@Mock
:创建一个模拟对象(mocked object)。对其进行设置后,@Mock
创建的对象会返回设置好的值而不是执行真实的逻辑。@Spy
:创建一个间谍对象(spied object)。它和真实的对象很类似,除非明确设定,否则都是调用真实的方法。@InjectMocks
:创建一个实例,其余用@Mock
(或@Spy
)注解创建的mock会被注入到用@InjectMocks
注解的类中。
Q1:既然@spy调用的是真实方法,那我为什么要使用这个注解呢,我直接在需要模拟的方法时候使用mock不就可以了吗
A:@Spy
是在你只想模拟类的部分行为,而其他方法还希望执行真实的逻辑时非常有用的。
@Spy
List<String> spyList = new ArrayList<String>();
@Test
public void testSpy() {
spyList.add("one");
spyList.add("two");
// 在spy对象上设定返回预期值
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
assertEquals("one", spyList.get(0));
}
1.3 @mockbean与@SpyBean
@MockBean
和@SpyBean
是 Spring Boot 测试中常用的两个注解。下面是它们各自的特性:
@MockBean
@MockBean
用于在 Spring 上下文中创建一个 Mock 对象,用它替换任何同类型的原有 Bean。@MockBean
创建的mock对象没有任何实际行为,只返回预设的值。- 使用
@MockBean
,你可以设置对其进行调用的各个方法的预期返回值。
@SpyBean
@SpyBean
用于在 Spring 上下文中创建一个 Spy 对象,用它替换任何同类型的原有的 Bean。@SpyBean
创建的 spy 对象基于实际的 Bean,事实上它就是原有 Bean 的一个复制体。除非明确地设定,否则它的方法调用结果与原有 Bean 完全相同。- 使用
@SpyBean
,你可以监视(或跟踪)Bean 的行为,并能在需要时们改变某些方法的行为。
总的来说,@MockBean
和 @SpyBean
的核心区别是:@MockBean
创建的是一个可配置的纯 mock 对象,而 @SpyBean
则是基于一个实际对象的副本,这个副本中的方法调用能够反映出原有对象的行为。
使用场景:
@MockBean使用场景和代码示例
使用场景:我们通常在Spring的集成测试中使用
@MockBean
,这个注解用于替换Spring应用程序上下文中的现有Bean。它创建了一个Mock实例,并使用它替换原本的Bean。@SpyBean使用场景和代码示例
使用场景:当我们希望监测真实的Bean时,我们使用
@SpyBean
。一个 Spy Bean 是一个代理,在这个代理中,被Spy的Bean的所有方法都会像平常一样被调用,除非我们明确地改变它们的行为。
1.4 verify的使用方法及场景
Mockito.verify() 方法是 Mockito 框架中用于验证 mock 对象方法调用的工具。通过 verify() 方法,我们可以检查 mock 对象的某个方法是否被调用了,并可以进一步验证调用次数、调用顺序、传入参数等情况。
下面是 Mockito.verify() 方法的基本使用方法及一些常见应用场景:
验证方法是否被调用:
// 创建 mock 对象
List<String> mockList = Mockito.mock(List.class);
// 调用被测试的方法
mockList.add("test");
// 验证方法 add 是否被调用过一次
Mockito.verify(mockList).add("test");
这两个方法里面的参数必须一样才能进行验证。如果你要验证的方法参数与实际调用的方法参数不一样,那么 Mockito.verify() 方法会抛出异常并提示参数不匹配。
验证方法被调用次数:
// 验证方法 add 被调用了两次
Mockito.verify(mockList, Mockito.times(2)).add("test");
验证方法未被调用:
// 验证方法 add 未被调用
Mockito.verify(mockList, Mockito.never()).add("test");
验证方法在特定顺序下被调用:
// 模拟方法调用
InOrder inOrder = Mockito.inOrder(mockList);
inOrder.verify(mockList).add("first");
inOrder.verify(mockList).add("second");
验证方法传入参数:
// 验证方法 add 是否被调用,并且传入参数为字符串 "hello"
Mockito.verify(mockList).add(Mockito.eq("hello"));
验证方法在特定时间内被调用:
// 验证方法 add 在100ms内是否被调用
Mockito.verify(mockList, Mockito.timeout(100)).add("test");
二、Junit5注解及断言
2.1 Junit5内置注解
2.2 junit5内置断言
三、Mockito 和 PowerMock的差异对比
Mockito 和 PowerMock 是两种广泛使用的 Java 单元测试模拟框架。它们都提供了创建和管理 mock 对象的能力,以帮助我们隔离并测试代码。然而,它们各自的特性使得它们在特定的场景中更有效。
Mockito 特性:
- 是目前最流行的 mocking 框架。
- 它能实现对方法的调用进行模拟,并验证方法的调用情况,通过一种优雅且简洁的 API 来提供这种功能。
- Mockito 不支持 mock 私有,静态和 fianl 方法或类的模拟。
- Mockito 的语法简单且易于阅读,代码可维护性更高。
- Mockito 更关注于“测试行为”,而非“测试结果”。
PowerMock 特性:
- PowerMock 是 Mockito 和 EasyMock 的扩展,它能够模拟更加复杂的场景,包括静态方法,构造器,final 类及方法,私有方法等。
- PowerMock 使用字节码操作技术,通过在运行时修改字节码的方式实现这些复杂的模拟。
- PowerMock 能够执行更深层次的测试,但复杂的特性可能导致代码难以阅读和维护。
- PowerMock 适合用来模拟那些传统上被视为不可模拟的代码。
两者的主要使用场景:
- Mockito 适合用于大多数标准的模拟情况,可以覆盖大部分的日常测试需求。其简单、易读的 API 使其成为首选工具。
- PowerMock 的使用场景则更为具体,如必须模拟静态方法、final 类/方法或私有方法等情况。不过使用 PowerMock 应慎重,这些能力常常提示代码设计存在问题。
使用powerMock模拟mock静态方法
public class UtilityClass {
static int staticMethod(long value) {
return 100;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(UtilityClass.class)
public class MockingStaticMethodTest {
@Test
public void testMockingStaticMethod() {
//模拟整个 UtilityClass 类
PowerMockito.mockStatic(UtilityClass.class);
//设置预期返回
PowerMockito.when(UtilityClass.staticMethod(5)).thenReturn(10);
//在你的测试代码里, 当 UtilityClass.staticMethod(5) 被调用时, 它现在将返回 10
int result = UtilityClass.staticMethod(5);
assertEquals(10, result);
//最后验证静态方法是否被调用
PowerMockito.verifyStatic(UtilityClass.class);
UtilityClass.staticMethod(5);
}
}