Mock概念
Mock叫做模拟对象,即用来模拟未被实现的对象可以预先定义这个对象在特定调用时的行为(例如返回值或抛出异常),从而模拟不同的系统状态。
导入Mock依赖
pom文件中引入springboot测试依赖,spring-boot-starter-test中包含了Mockito
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Mock测试环境和Spring上下文环境
仅使用Mock环境
使用Mock进行测试时候,可以仅仅使用Mock环境,不添加@SpringBootTest,这个时候不会加载Spring上下文(@Autowired等不会起作用),需要手动处理使用@Mock和@InjectMock来处理类之间的依赖关系。
常用注解:
@Mock:
@Mock
是 Mockito 提供的注解,用于生成模拟对象,是创建了一个新的对象。这里的 userDao
和 webServiceClient
是通过 Mockito 模拟的对象,而不是 Spring 容器中的实际 bean。它们的行为可以通过 when/thenReturn
或其他模拟方法来定义。
注意:当你使用 Mockito 的 @Mock
注解来 mock 一个类时,即使该类已经实现了部分方法,Mockito 也会拦截这些方法的调用。这意味着,默认情况下,Mockito 会模拟这个类的所有方法(包括已经实现的方法),除非你显式定义模拟行为。
因此,当你通过 @Mock
来 mock 一个已经实现部分方法的类时:
- 如果你调用了已经实现的方法,并且没有为这个方法定义具体的
when/thenReturn
行为,Mockito 会返回 默认值(例如null
、0、false
等),而不会执行类中的实际实现。 - 如果你想让某些方法在调用时执行它们的实际实现,你需要使用
Mockito
提供的spy()
功能。
@Spy
@Spy
创建的对象是真实对象的部分模拟(Partial Mock),它会调用对象的真实方法,而只有那些明确模拟的方法才会被替换成模拟的行为。spy()
提供部分模拟功能。未被显式模拟的方法将调用实际实现,已经被模拟的方法则返回预设的模拟值。
在使用 @InjectMocks
时,Mockito 会将 @Mock
和 @Spy
注解的对象注入到被测试的对象中。如果某个依赖项使用了 @Spy
,Mockito 会确保被注入的是该对象的部分模拟实现。
spy()
与 mock()
的对比
特性
mock()
spy()
默认行为
模拟所有方法,返回默认值(如 null
)
调用真实的实现,除非被显式模拟
是否执行实际代码
不执行
执行实际的代码实现
定义模拟行为时是否拦截
会拦截并返回模拟值
如果定义了模拟行为,使用模拟值,没定义则执行实际实现
@InjectMocks:
@InjectMocks
是 Mockito 的一个注解,用于将模拟对象(即用@Mock
创建的对象)注入到被测对象中(这里是UserService
)。- Mockito 会创建一个新的
UserService
对象,并将userDao
和webServiceClient
作为依赖注入到这个新的对象中。 - 这与 Spring 容器的行为无关。即使
UserService
已经通过@Service
注解注册到了 Spring 容器中,在使用@InjectMocks
时,Mockito 会创建并管理一个全新的UserService
对象。
具体演示:
//UserDao定义
public class UserDao {
//getUserById有真实的实现
User getUserById(int userId)
{
return new User(1,"张三");
}
int saveUser(User user);
}
//WebServiceClient定义
public class WebServiceClient {
boolean isServiceAvailable();
String getUserDataFromWebService(int userId);
}
@Service
//UserService依赖于UserDao以及WebServiceClient
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private WebServiceClient webServiceClient;
//省略操作
}
//可以没有@SpringBootTest
public class UserServiceTest {
// 使用@Spy部分模拟UserDao对象
@Spy
private UserDao userDao;
// 模拟WebServiceClient对象
@Mock
private WebServiceClient webServiceClient;
// 根据依赖将mock对象注入到UserService中
@InjectMocks
private UserService userService;
//必须首先初始化
@BeforeEach
public void setUp() {
//非常重要!!!!!
MockitoAnnotations.openMocks(this); // 初始化Mockito
}
@Test
void Spytest()
{
//模拟saveUser方法,而调用getUserById为其真实实现
doReturn(1).when(userDao).saveUser(any(User.class));
//真实行为
User FoundUser = userDao.getUserById(1);// User张三
//模拟行为
userDao.saveUser(FoundUser);// 返回1
}
//省略其他测试方法
}
MockitoAnnotations.openMocks(this)作用:
MockitoAnnotations.openMocks(this)
是用于初始化 @Mock
、@Spy
、@InjectMocks
注解的关键步骤。如果没有这行代码,Mockito 将不会创建和初始化这些模拟对象,导致测试失败。
MockitoAnnotations.openMocks(this)
适用于非 Spring 环境下的单元测试。在 Spring Boot 测试中,你通常使用 @MockBean
或 @Autowired
,Spring Boot 会自动处理模拟对象的初始化,因此不需要调用这个方法。
搭配Spring上下文
使用Spring上下文需要使用@MockBean来在测试中将 Spring 容器中的某些 bean 替换为 Mockito 模拟的对象,然后可以使用@Autowired处理类之间的依赖关系。
结合 Spring Boot 和 Mockito 的测试方法
- 使用
@MockBean
:用来替换 Spring 容器中的 bean,模拟它的行为。 - 使用
@Autowired
:注入 Spring 容器中实际的服务(如UserService
)。 - 使用
@SpringBootTest
:启动 Spring Boot 的测试上下文。
常用注解:
@MockBean:
@MockBean
是 Spring Boot 提供的注解,用于创建一个 Mockito 模拟对象,并将它替换到 Spring 上下文中。userDao
和 webServiceClient
是通过 @MockBean
模拟的对象,而不是真实的对象。这些模拟对象将替换 Spring 容器中的相应 bean,然后可以通过@Autowird自动注入被依赖类中。
@SpyBean:
@SpyBean
是 Spring Boot 提供的一个注解,专门用于 部分模拟(Partial Mocking) Spring 容器中的 Bean。它的作用是创建一个部分模拟的对象,部分调用真实方法,部分进行模拟(Mock)行为。相比于 Mockito 提供的 @Spy
,@SpyBean
更加集成到 Spring 环境中,并且允许你将某个 Spring 容器中的 Bean 替换为部分模拟对象。
@SpyBean
的工作原理
- 部分模拟:
@SpyBean
允许你对 Spring 容器中的现有 Bean 进行部分模拟。这意味着模拟的 Bean 会保留其大部分原始行为,只有你明确模拟的部分会改变。 - 注入到 Spring 容器中:使用
@SpyBean
时,Spring Boot 会将该部分模拟的 Bean 注入到 Spring 容器中,替换原有的 Bean。
具体演示:
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private WebServiceClient webServiceClient;
//省略操作
}
@SpringBootTest //使用Spring上下文
public class UserServiceTest {
// 通过Spring容器管理的UserService实例
@Autowired
private UserService userService;
// 部分模拟UserDao对象,替换Spring容器中的UserDao bean
@SpyBean
private UserDao userDao;
// 模拟WebServiceClient对象,替换Spring容器中的WebServiceClient bean
@MockBean
private WebServiceClient webServiceClient;
//被替换的两个Mock对象可以被Spring容器自动注入到userService中
//省略测试方法
}
注意:
当你使用 @MockBean
与@SpyBean
注解时,不需要 调用 MockitoAnnotations.openMocks(this);
,因为 @MockBean
是由 Spring Boot 管理的,Spring Boot 会自动初始化并处理 @MockBean
创建的模拟对象。
测试流程:
使用 Mockito 进行测试的一般流程可以分为以下几个步骤:
- 设置测试环境:在单元测试中,通过 Mockito 的注解或者方法来创建模拟对象(Mock)。模拟对象是用于替代真实的依赖,以便控制和测试不同的场景。
- 定义模拟行为:使用 Mockito 的方法定义模拟对象的方法行为。通常通过
when(...).thenReturn(...)
来模拟返回特定值,或者使用doThrow()
来模拟异常抛出。 - 执行测试代码:编写业务逻辑代码,将模拟对象注入依赖进行测试并断言结果。
- 验证行为:使用
verify()
验证方法调用、参数、调用次数等。
1. 准备测试环境
在测试类中准备需要的模拟对象和被测对象。可以通过 @Mock
、@InjectMocks
注解或者 Mockito.mock()
方法手动创建模拟对象。对于 Spring 项目,还可以使用 @MockBean
来替代 Spring 容器中的 Bean。
2、定义模拟行为:
- 使用
when(...).thenReturn(...)
来模拟方法返回值。 - 使用
doReturn(...).when(...)
来避免方法的真实调用。 - 使用
thenThrow(...)
或doThrow(...).when(...)
来模拟异常。 - 使用
thenAnswer(...)
来处理复杂的动态行为。 - 使用
doNothing()
来处理void
方法。
1. 使用 when(...).thenReturn(...)
来模拟方法返回值
这是最常用的方式。适用于模拟方法调用后需要返回某个特定值的情况。
示例:模拟 UserDao
的 getUserById()
方法在调用时返回特定的 User
对象。
@Mock
private UserDao userDao;
@Test
public void testGetUser() {
// 模拟getUserById方法,当传入用户ID为1时,返回一个新的User对象
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
// 调用测试方法
User user = userDao.getUserById(1);
// 断言结果
assertEquals("John", user.getName());
}
when(...).thenReturn(...)
:表示当调用 userDao.getUserById(1)
时,返回 new User(1, "John")
。
2. 使用 doReturn(...).when(...)
来模拟方法返回值
与 when(...).thenReturn(...)
类似,但适用于某些特殊情况,例如需要避免实际调用真实方法(尤其是部分模拟 @Spy
时),或者处理 void
方法的情况。
示例:使用 doReturn
避免部分模拟的真实方法被调用。
@Spy
private UserDao userDao;
@Test
public void testSaveUser() {
// 避免调用真实的saveUser方法
doReturn(1).when(userDao).saveUser(any(User.class));
// 调用saveUser方法
userDao.saveUser(new User(2, "Doe"));
// 验证saveUser确实被调用过一次
verify(userDao, times(1)).saveUser(any(User.class));
}
doReturn(...).when(...)
:适用于你不希望真实调用某个方法的情况。
when.thenReturn和doReturn.when的区别
1. when(...).thenReturn(...)
这是 Mockito 的标准使用方式,用于定义当某个方法被调用时返回指定的值。它适用于绝大多数场景,尤其是当你使用完全模拟对象(即 @Mock
)时。
这种写法会去实际执行代码,然后返回指定值
示例:
when(userDao.getUserById(2)).thenReturn(null);
- 工作原理:Mockito 在内部是通过调用
userDao.getUserById(2)
方法,并在该方法执行后记录这个调用,然后当方法被再次调用时,返回null
。 - 调用时机:
when(...).thenReturn(...)
实际上会首先调用目标方法getUserById(2)
,然后再返回指定的结果。如果目标方法是有副作用的(比如修改某些状态),它会先执行副作用,再进行模拟。
2. doReturn(...).when(...)
doReturn(...).when(...)
是 Mockito 中的另一种方法,主要用于避免方法调用本身带来的副作用,尤其是在 部分模拟(@Spy
)的场景中非常有用。
这种写法不会执行代码,直接返回指定值。
示例:
doReturn(null).when(userDao).getUserById(2);
- 工作原理:
doReturn(null)
先定义了模拟的返回值,然后使用when(userDao)
来指定在getUserById(2)
方法被调用时返回null
,而不会先调用getUserById(2)
方法的真实实现。 - 调用时机:
doReturn(...).when(...)
不会实际调用目标方法,因此不会触发任何真实方法的执行。如果目标方法有副作用或复杂的逻辑,使用doReturn(...)
可以避免这些问题。
什么时候使用 doReturn(...).when(...)
?
-
处理
void
方法:when(...).thenReturn(...)
不能用于模拟void
方法,因为void
方法没有返回值。这时你需要使用doReturn()
或doThrow()
来模拟void
方法的行为。示例:
doNothing().when(mockObject).someVoidMethod();
-
部分模拟(
@Spy
)的场景:当你使用部分模拟(@Spy
)时,when(...).thenReturn(...)
实际上会调用真实方法。如果你不希望调用真实方法(比如该方法会改变对象状态或有副作用),可以使用doReturn(...)
来避免真实方法的调用。示例:
@Spy private UserDao userDao; // 避免真实调用 doReturn(null).when(userDao).getUserById(2);
在这种情况下,
when(userDao.getUserById(2)).thenReturn(null)
会实际调用getUserById(2)
,但使用doReturn(null)
则不会调用真实方法。 -
方法抛出异常的场景:某些情况下,方法在实际调用时会抛出异常。如果你不希望方法抛出异常(例如,你只关心返回结果的模拟),使用
doReturn(...)
可以避免直接调用导致的异常。示例:
doReturn(null).when(userDao).getUserById(2);
如果
userDao.getUserById(2)
的真实方法抛出了异常,而你希望避免这种情况,则使用doReturn(...)
可以跳过真实方法调用。
when(...).thenReturn(...)
的局限性
会实际调用方法:如果目标方法会触发某些副作用(例如修改数据或引发异常),when(...).thenReturn(...)
会首先调用该方法,然后记录返回结果,这有时不是你想要的行为,特别是在 @Spy
场景中。
示例:当使用部分模拟(@Spy
)时,以下代码会先调用 getUserById(2)
,即真实方法会被调用:
when(userDao.getUserById(2)).thenReturn(null);
不能用于 void
方法:因为 when(...).thenReturn(...)
是针对有返回值的方法,如果你想模拟 void
方法(即不返回值的方法),则需要使用 doReturn()
、doThrow()
等方法。
3. 模拟方法抛出异常
可以使用 thenThrow(...)
或 doThrow(...).when(...)
来模拟方法在被调用时抛出异常的场景,适合用于测试异常处理逻辑。
示例:模拟 saveUser
方法在调用时抛出异常。
@Mock
private UserDao userDao;
@Test
public void testSaveUserThrowsException() {
// 模拟saveUser方法抛出异常
doThrow(new RuntimeException("Database error")).when(userDao).saveUser(any(User.class));
// 捕获异常
assertThrows(RuntimeException.class, () -> {
userDao.saveUser(new User(1, "John"));
});
// 验证saveUser方法确实被调用过一次
verify(userDao, times(1)).saveUser(any(User.class));
}
doThrow(...).when(...)
或 thenThrow(...)
:模拟方法抛出异常,用于测试异常处理逻辑。
4. 模拟 void
方法的行为
doNothing()
和 doThrow()
是最常用的处理 void
方法的方式,前者模拟不执行任何操作,后者模拟抛出异常。
示例:模拟 void
方法 deleteUser
执行时不做任何事情。
@Mock
private UserDao userDao;
@Test
public void testDeleteUser() {
// 模拟deleteUser方法执行时什么都不做
doNothing().when(userDao).deleteUser(anyInt());
// 调用测试方法
userDao.deleteUser(1);
// 验证deleteUser确实被调用过
verify(userDao, times(1)).deleteUser(1);
}
doNothing().when(...)
:用于模拟 void
方法的行为,表示该方法什么都不做。
5. 使用 thenAnswer(...)
来模拟复杂行为
thenAnswer()
允许你根据传入的参数、方法的调用上下文、甚至外部状态来动态地生成返回值或执行特定逻辑。相比于 thenReturn()
这种简单的返回值模拟方式,thenAnswer()
提供了更大的灵活性。
thenAnswer()
的主要特点:
- 基于输入参数动态响应:你可以根据方法的输入参数来生成不同的返回结果。
- 执行自定义逻辑:它允许你在模拟方法中执行特定的自定义逻辑,而不仅仅是返回一个固定值。
- 复杂行为模拟:适用于更复杂的业务场景,比如多个条件组合下的不同返回值,或者需要根据传入参数执行计算等。
thenAnswer()
使用方法
thenAnswer()
接受一个 Answer
接口的实现作为参数。Answer
接口定义了一个 answer(InvocationOnMock invocation)
方法,该方法会在模拟方法被调用时执行。你可以通过这个方法来访问方法的调用信息(包括传入的参数),并根据需要自定义返回结果或逻辑。
//Answer 接口定义
public interface Answer<T> {
T answer(InvocationOnMock invocation) throws Throwable;
}
InvocationOnMock
:提供了对当前调用的所有信息,包括参数、调用的 mock 对象等。answer()
:在方法被调用时触发,用于自定义返回值或执行逻辑。
InvocationOnMock
接口提供了几个常用的方法,允许你访问模拟方法调用的详细信息:
getMock()
:返回当前被调用的模拟对象。getMethod()
:返回被调用的Method
对象。getArguments()
:返回方法的所有传递参数的数组。getArgument(int index)
:返回指定索引位置的单个参数。getArgument(int index, Class<T> clazz)
:返回指定索引的参数并强制转换为指定类型。getArgumentsCount()
:返回传递的参数数量。callRealMethod()
:调用被模拟方法的真实实现(常用于部分模拟@Spy
)。
示例:根据传入的参数动态返回不同结果
假设有一个 UserDao
的 getUserById
方法,你希望根据传入的用户 ID 来动态地返回不同的用户对象。
@Mock
private UserDao userDao;
@Test
public void testGetUserWithAnswer() {
// 使用thenAnswer根据传入的参数返回不同的结果
when(userDao.getUserById(anyInt())).thenAnswer(new Answer<User>() {
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
// 获取传入的参数(即用户ID)
int userId = invocation.getArgument(0);
// 根据用户ID返回不同的User对象
return new User(userId, "User" + userId);
}
});
// 调用测试方法
User user1 = userDao.getUserById(1);
User user2 = userDao.getUserById(2);
// 验证返回结果
assertEquals("User1", user1.getName());
assertEquals("User2", user2.getName());
}
解释:
thenAnswer(new Answer<User>() {...})
:在getUserById
方法被调用时,触发Answer
接口的answer()
方法来生成返回值。invocation.getArgument(0)
:获取方法调用时的第一个参数,这里是传入的userId
。- 动态生成返回结果:根据传入的
userId
,返回不同的User
对象。
示例:抛出异常的场景
假设你想根据方法的传入参数决定是否抛出异常,可以使用 thenAnswer()
来实现。
@Mock
private UserDao userDao;
@Test
public void testGetUserThrowsException() {
// 使用thenAnswer来模拟不同的异常抛出条件
when(userDao.getUserById(anyInt())).thenAnswer(new Answer<User>() {
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
int userId = invocation.getArgument(0);
if (userId < 0) {
throw new IllegalArgumentException("User ID cannot be negative");
}
return new User(userId, "User" + userId);
}
});
// 验证抛出异常
assertThrows(IllegalArgumentException.class, () -> {
userDao.getUserById(-1);
});
// 正常调用不抛异常
User user = userDao.getUserById(1);
assertEquals("User1", user.getName());
}
解释:
- 根据传入参数抛出异常:当传入的
userId
小于 0 时,抛出IllegalArgumentException
,否则正常返回用户对象。 - 测试不同情况:我们通过
assertThrows
来验证方法在传入非法参数时抛出了预期的异常。
复杂场景:模拟调用次数或外部状态的变化
有时,你可能需要根据方法被调用的次数或外部状态的变化来决定返回值或执行逻辑。thenAnswer()
可以处理这些复杂场景。
示例:根据调用次数返回不同的结果
@Mock
private UserDao userDao;
@Test
public void testGetUserWithMultipleCalls() {
// 使用thenAnswer来根据调用次数返回不同的结果
when(userDao.getUserById(anyInt())).thenAnswer(new Answer<User>() {
private int callCount = 0; // 记录调用次数
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
callCount++;
if (callCount == 1) {
return new User(1, "First Call User");
} else {
return new User(1, "Subsequent Call User");
}
}
});
// 第一次调用返回"First Call User"
User user1 = userDao.getUserById(1);
assertEquals("First Call User", user1.getName());
// 第二次及之后的调用返回"Subsequent Call User"
User user2 = userDao.getUserById(1);
assertEquals("Subsequent Call User", user2.getName());
}
解释:
callCount
:通过一个计数器callCount
来记录方法的调用次数。- 根据调用次数返回不同的结果:第一次调用返回一个结果,后续调用返回不同的结果。
thenAnswer()
的优势
- 灵活性高:
thenAnswer()
允许你在方法被调用时基于参数或其他条件生成返回值或抛出异常,比简单的thenReturn()
更加灵活。 - 动态行为模拟:可以根据方法的参数、调用次数、甚至外部状态来决定模拟的行为,适用于复杂的测试场景。
- 测试特定逻辑:它可以帮助你测试依赖复杂逻辑的代码片段,尤其是在简单的
thenReturn()
或thenThrow()
无法满足需求时。
3. 执行测试代码并断言
编写测试代码,调用被测类中的方法。由于被测类的依赖已经被模拟对象替换,所以你可以专注于测试当前方法的逻辑,而不必担心真实依赖带来的副作用。在执行完测试代码后,你可以通过 JUnit 的断言 来检查测试结果是否符合预期。常用的断言包括 assertEquals()
、assertTrue()
、assertNull()
等。
JUnit 常用的断言方法
在 JUnit 5 中,所有断言方法都位于 org.junit.jupiter.api.Assertions
类中。常见的断言包括:
assertEquals(expected, actual)
:断言两个值是否相等。assertNotEquals(unexpected, actual)
:断言两个值是否不相等。assertTrue(condition)
:断言条件为true
。assertFalse(condition)
:断言条件为false
。assertNull(object)
:断言对象是否为null
。assertNotNull(object)
:断言对象是否不为null
。assertSame(expected, actual)
:断言两个对象引用是否指向同一个对象。assertNotSame(unexpected, actual)
:断言两个对象引用是否不指向同一个对象。assertThrows(expectedType, executable)
:断言执行代码时抛出特定类型的异常。assertTimeout(duration, executable)
:断言在指定的时间内执行完成。
1. assertEquals(expected, actual)
assertEquals()
用于验证两个值是否相等,通常用于比较基本类型或重写了 equals()
方法的对象。
示例:
@Test
public void testAssertEquals() {
int expected = 42;
int actual = 42;
assertEquals(expected, actual); // 断言通过,两个值相等
}
可以带一个消息参数,方便调试:
assertEquals(expected, actual, "The values should be equal.");
2. assertNotEquals(unexpected, actual)
assertNotEquals()
用于验证两个值是否不相等。
示例:
@Test
public void testAssertNotEquals() {
String actual = "Hello";
String unexpected = "Goodbye";
assertNotEquals(unexpected, actual); // 断言通过,两个值不相等
}
3. assertTrue(condition)
assertTrue()
用于验证条件是否为 true
。
示例:
@Test
public void testAssertTrue() {
boolean condition = 5 > 3;
assertTrue(condition); // 断言通过,条件为 true
}
可以带自定义消息:
assertTrue(condition, "The condition should be true.");
4. assertFalse(condition)
assertFalse()
用于验证条件是否为 false
。
示例:
@Test
public void testAssertFalse() {
boolean condition = 5 < 3;
assertFalse(condition); // 断言通过,条件为 false
}
5. assertNull(object)
assertNull()
用于验证对象是否为 null
。
示例:
@Test
public void testAssertNull() {
String value = null;
assertNull(value); // 断言通过,value 为 null
}
6. assertNotNull(object)
assertNotNull()
用于验证对象是否不为 null
。
示例:
@Test
public void testAssertNotNull() {
String value = "JUnit";
assertNotNull(value); // 断言通过,value 不为 null
}
7. assertSame(expected, actual)
assertSame()
用于验证两个对象是否指向同一个引用(即,比较两个对象的内存地址是否相同)。
示例:
@Test
public void testAssertSame() {
String str1 = "JUnit";
String str2 = str1; // 两个引用指向同一个对象
assertSame(str1, str2); // 断言通过
}
8. assertNotSame(unexpected, actual)
assertNotSame()
用于验证两个对象引用是否不相同。
示例:
@Test
public void testAssertNotSame() {
String str1 = new String("JUnit");
String str2 = new String("JUnit");
assertNotSame(str1, str2); // 断言通过,两个引用不相同
}
9. assertThrows(expectedType, executable)
assertThrows()
用于验证某段代码是否抛出了特定类型的异常。
示例:
@Test
public void testAssertThrows() {
assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("Invalid argument");
});
}
如果没有抛出异常或抛出了不同类型的异常,测试将失败。
10. assertTimeout(duration, executable)
assertTimeout()
用于验证某段代码是否在指定时间内完成。如果代码执行时间超过了指定的时间,测试将失败。
示例:
import java.time.Duration;
@Test
public void testAssertTimeout() {
assertTimeout(Duration.ofSeconds(1), () -> {
// 模拟耗时操作
Thread.sleep(500);
});
}
其他断言方法
1. assertArrayEquals(expectedArray, actualArray)
用于验证两个数组是否相等。
示例:
@Test
public void testAssertArrayEquals() {
int[] expectedArray = {1, 2, 3};
int[] actualArray = {1, 2, 3};
assertArrayEquals(expectedArray, actualArray); // 断言通过
}
2. assertIterableEquals(expected, actual)
用于验证两个 Iterable
对象是否相等。
示例:
@Test
public void testAssertIterableEquals() {
List<String> expected = Arrays.asList("a", "b", "c");
List<String> actual = Arrays.asList("a", "b", "c");
assertIterableEquals(expected, actual); // 断言通过
}
3. assertLinesMatch(expected, actual)
用于验证两个字符串列表的每一行是否匹配。常用于多行文本的比较。
示例:
@Test
public void testAssertLinesMatch() {
List<String> expectedLines = Arrays.asList("line1", "line2");
List<String> actualLines = Arrays.asList("line1", "line2");
assertLinesMatch(expectedLines, actualLines); // 断言通过
}
断言的灵活性
大多数 JUnit 断言方法都可以带上一个可选的第三个参数,表示失败时的自定义消息。自定义消息有助于在调试时快速找到问题。例如:
assertEquals(42, actualValue, "The actual value should be 42.");
当断言失败时,会输出 "The actual value should be 42."
这样的提示,帮助你快速定位问题。
4、验证行为:
验证行为的常用方法
verify()
:验证某个模拟对象的方法是否被调用。verifyNoMoreInteractions()
:验证某个模拟对象的方法在指定的调用之外,没有其他额外的调用。verifyZeroInteractions()
/verifyNoInteractions()
:验证某个模拟对象从未被调用。InOrder
:验证多个方法调用的顺序。times()
:验证某个方法被调用的次数。never()
:验证某个方法从未被调用。atLeast()
和atMost()
:验证某个方法至少/至多被调用多少次。
1. 使用 verify()
验证方法调用
verify()
是 Mockito 验证调用关系的核心方法。它用于验证某个模拟对象的特定方法是否被调用。
示例:验证方法被调用
@Mock
private UserDao userDao;
@Test
public void testVerifyMethodCall() {
// 模拟getUserById方法的行为
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
// 调用被测方法
User user = userDao.getUserById(1);
// 验证getUserById方法是否被调用过
verify(userDao).getUserById(1);
}
在这个例子中,verify(userDao).getUserById(1)
验证了 getUserById(1)
是否被调用了一次。
2. 使用 times()
验证调用次数
有时,你不仅要验证方法是否被调用,还要验证它被调用了几次。Mockito 提供了 times()
方法来验证调用的次数。
示例:验证方法被调用的次数
@Mock
private UserDao userDao;
@Test
public void testVerifyCallTimes() {
// 模拟调用行为
when(userDao.getUserById(1)).thenReturn(new User(1, "John"));
// 调用多次方法
userDao.getUserById(1);
userDao.getUserById(1);
// 验证getUserById方法被调用了2次
verify(userDao, times(2)).getUserById(1);
}
times(2)
:验证 getUserById(1)
被调用了两次。
3. 使用 never()
验证方法从未被调用
如果你需要验证某个方法从未被调用过,可以使用 never()
。
示例:验证方法从未被调用
@Mock
private UserDao userDao;
@Test
public void testVerifyNeverCalled() {
// 调用其他方法
userDao.saveUser(new User(1, "John"));
// 验证getUserById方法从未被调用
verify(userDao, never()).getUserById(1);
}
never()
:验证 getUserById(1)
从未被调用过。
4. 使用 verifyNoMoreInteractions()
验证没有其他方法调用
verifyNoMoreInteractions()
用于确保在验证指定的方法调用之后,没有其他不必要的调用。
示例:验证没有额外的调用
@Mock
private UserDao userDao;
@Test
public void testVerifyNoMoreInteractions() {
// 调用一个方法
userDao.getUserById(1);
// 验证getUserById方法被调用过
verify(userDao).getUserById(1);
// 验证没有其他多余的方法调用
verifyNoMoreInteractions(userDao);
}
verifyNoMoreInteractions()
:确保除了 getUserById(1)
外,userDao
没有其他方法被调用。
5. 使用 verifyZeroInteractions()
或 verifyNoInteractions()
验证没有任何交互
verifyZeroInteractions()
(或 verifyNoInteractions()
)用于验证某个模拟对象的任何方法都没有被调用过。
示例:验证没有任何方法调用
@Mock
private UserDao userDao;
@Test
public void testVerifyZeroInteractions() {
// 不调用任何方法
// 验证userDao没有任何方法被调用
verifyZeroInteractions(userDao);
// 或者使用 verifyNoInteractions(userDao);
}
verifyZeroInteractions()
或 verifyNoInteractions()
:验证 userDao
没有任何方法被调用。
6. 使用 InOrder
验证调用顺序
InOrder
用于验证方法的调用顺序。如果你有多个方法调用,并且需要确保它们按特定顺序调用,InOrder
非常有用。
示例:验证方法调用的顺序
@Mock
private UserDao userDao;
@Mock
private NotificationService notificationService;
@Test
public void testVerifyCallOrder() {
// 调用多个方法
userDao.saveUser(new User(1, "John"));
notificationService.notifyUser(1);
// 验证调用顺序
InOrder inOrder = inOrder(userDao, notificationService);
inOrder.verify(userDao).saveUser(any(User.class)); // 验证saveUser先被调用
inOrder.verify(notificationService).notifyUser(1); // 然后notifyUser被调用
}
inOrder()
:验证 saveUser
和 notifyUser
方法是否按照指定的顺序被调用。
7. 使用 atLeast()
和 atMost()
验证调用的最小/最大次数
如果你需要验证某个方法被调用的次数在某个范围内,Mockito 提供了 atLeast()
和 atMost()
来验证方法的最少和最多调用次数。
示例:验证调用的最小次数
@Mock
private UserDao userDao;
@Test
public void testVerifyAtLeast() {
// 调用方法多次
userDao.getUserById(1);
userDao.getUserById(1);
userDao.getUserById(1);
// 验证getUserById方法至少被调用2次
verify(userDao, atLeast(2)).getUserById(1);
}
atLeast(2)
:验证 getUserById(1)
至少被调用了 2 次。
示例:验证调用的最大次数
@Mock
private UserDao userDao;
@Test
public void testVerifyAtMost() {
// 调用方法多次
userDao.getUserById(1);
userDao.getUserById(1);
// 验证getUserById方法最多被调用2次
verify(userDao, atMost(2)).getUserById(1);
}
atMost(2)
:验证 getUserById(1)
最多被调用了 2 次。
具体演示:
class UserServiceTest {
//1、准备测试环境
@Mock
WebServiceClient webServiceClient;
@Mock
UserDao userDao;
@InjectMocks
UserService userService;
//初始化Mock
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
//测试用例,从本地获取用户数据
@Test
void getLocalDBUser() {
User MockUser = User.builder().id(1).name("张三").build();
//2、定义模拟行为
when(userDao.getUserById(1)).thenReturn(MockUser);
//3、业务测试代码
User ReturnedUser = userService.getUser(1);
//断言结果
assertEquals("张三",ReturnedUser.getName());
//4、验证行为
verify(userDao,times(1)).getUserById(1);
}
}