SpringBoot整合Mockito进行单元测试超全详细教程 JUnit断言 Mockito 单元测试
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 {@Autowiredprivate UserDao userDao;@Autowiredprivate WebServiceClient webServiceClient;//省略操作
}//可以没有@SpringBootTest
public class UserServiceTest {// 使用@Spy部分模拟UserDao对象@Spyprivate UserDao userDao;// 模拟WebServiceClient对象@Mockprivate WebServiceClient webServiceClient;// 根据依赖将mock对象注入到UserService中@InjectMocksprivate UserService userService;//必须首先初始化@BeforeEachpublic void setUp() {//非常重要!!!!!MockitoAnnotations.openMocks(this); // 初始化Mockito}@Testvoid 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 {@Autowiredprivate UserDao userDao;@Autowiredprivate WebServiceClient webServiceClient;//省略操作
}@SpringBootTest //使用Spring上下文
public class UserServiceTest {// 通过Spring容器管理的UserService实例@Autowiredprivate UserService userService;// 部分模拟UserDao对象,替换Spring容器中的UserDao bean@SpyBeanprivate UserDao userDao;// 模拟WebServiceClient对象,替换Spring容器中的WebServiceClient bean@MockBeanprivate 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>() {@Overridepublic 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>() {@Overridepublic 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; // 记录调用次数@Overridepublic 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、准备测试环境@MockWebServiceClient webServiceClient;@MockUserDao userDao;@InjectMocksUserService userService;//初始化Mock@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);}//测试用例,从本地获取用户数据@Testvoid 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);}
}
相关文章:

SpringBoot整合Mockito进行单元测试超全详细教程 JUnit断言 Mockito 单元测试
Mock概念 Mock叫做模拟对象,即用来模拟未被实现的对象可以预先定义这个对象在特定调用时的行为(例如返回值或抛出异常),从而模拟不同的系统状态。 导入Mock依赖 pom文件中引入springboot测试依赖,spring-boot-start…...

【AI知识】过拟合、欠拟合和正则
一句话总结: 过拟合和欠拟合是机器学习中的两个相对的概念,正则化是用于解决过拟合的方法。 1. 欠拟合: 指模型在训练数据上表现不佳,不能充分捕捉数据的潜在规律,导致在训练集和测试集上的误差都很高。欠拟合意味着模…...

MacOS编译webRTC源码小tip
简单记录一下,本人在编译webRTC时,碰到了一下比较烦人的问题,在MacOS终端下,搭建科学上网之后,chromium的depot_tools仓库成功拉下来了,紧接着,使用fetch以及gclient sync始终都返回curl相关的网…...

Linux基础命令(三):文件压缩及解压缩命令
文件压缩及解压缩命令 tar — 打包和压缩 tar 是一个用于打包文件的工具,常常用来将多个文件或目录打包成一个单独的文件。它本身不进行压缩,但可以与压缩工具(如 gzip 或 bzip2)一起使用。 用法: 打包文件࿰…...

目标跟踪算法:ByteTrack、卡尔曼滤波、匈牙利算法、高置信度检测目标、低置信度检测目标
目录 1 ByteTrack特点 2 ByteTrack和SORT区别----个人通俗理解 3 ByteTrack算法原理 4 ByteTrack整体流程图 上一篇博客我复习了下SORT跟踪算法,这一篇博客我再复习下ByteTrack跟踪算法,ByteTrack里面也是用了卡尔曼滤波和匈牙利算法&#x…...

[定昌linux系统]如何安装jdk8
1:下载jdk8 的 arm64 的版本,由于官方下载需要gmail,我的gmail 密码忘了,所以从csdn上下载了一份,地址: https://download.csdn.net/download/qq_27742163/88533548?utm_mediumdistribute.pc_relevant_download.none…...

【Cadence32】PCB多层板电源、地平面层创建心得➕CM约束管理器Analyze分析显示设置➕“DP”报错DRC
【转载】Cadence Design Entry HDL 使用教程 【Cadence01】Cadence PCB Edit相对延迟与绝对延迟的显示问题 【Cadence02】Allegro引脚焊盘Pin设置为透明 【Cadence03】cadence不小心删掉钢网层怎么办? 【Cadence04】一般情况下Allegro PCB设计时的约束规则设置&a…...

基于SpringBoot+Vue的新闻管理系统
系统展示 用户前台界面 管理员后台界面 系统背景 随着互联网技术的飞速发展,信息传播速度不断加快,新闻媒体行业面临着巨大的机遇与挑战。传统的新闻媒体正在逐渐向数字化转型,而新闻管理系统作为数字化新闻媒体的核心组成部分,其…...
图的割点、割边(Tarjan算法)
深度优先搜索的利用。 在一个无向连通图中,如果删掉某个顶点后,图不再连通(即任意两点之间不能互相到达),我们称这样的顶点为割点。 在一个无向连通图中,如果删掉某条边后,图不在连通࿰…...

算法学习(十四)—— 二叉树的深度搜索(DFS)
目录 关于dfs 部分OJ题详解 2331. 计算布尔二叉树的值 129. 求根节点到叶节点数字之和 814. 二叉树剪枝 98. 验证二叉搜索树 230. 二叉搜索树中第K小的元素 257. 二叉树的所有路径 关于dfs 算法学习(十二)—— 递归,搜索,…...

【vue2】封装自定义的日历组件(三)之基础添加月份的加减定位到最新月份的第一天
我们在切换月份的时候,希望高亮显示在每个月的第一天上面,这样的效果我们要怎么来实现,其实也很简单,我们先看下实现的效果 实现效果 代码实现 原理就是获取到每月的第一天日期,然后再跟整个的数据进行对比ÿ…...

LabVIEW偏心圆筒流变仪测控系统
偏心圆筒流变仪是一种专门研究聚合物熔体在复杂流场中特殊流变行为的先进设备。通过结合硬件控制与LabVIEW软件开发,本系统实现了对流变仪功能的精准控制与数据采集,进一步提高了聚合物加工过程的研究精度和效率。 项目背景 传统的流变测量设备多集中于…...

Runloop
假设你的项目中有关tableView,然后还有一个定时器timer在执行,定时器代码如下: var num 0override func viewDidLoad() {super.viewDidLoad()let timer Timer(timeInterval: 1,target: self,selector: #selector(self.run),userInfo: nil,r…...

SpringBoot的Bean类三种注入方式(附带LomBok注入)
SpringBoot的Bean类三种注入方式(附带LomBok注入) 在 Spring Boot 中,Bean 的注入方式主要包括构造函数注入(Constructor Injection)、字段注入(Field Injection)以及 Setter 方法注入…...

开源向量数据库介绍说明
开源向量数据库 Milvus 特点:分布式、高性能,支持亿级向量检索。 支持的数据类型:文本、图像、音频、视频等。 使用场景:推荐系统、语义搜索、图像搜索。 数据存储后端:支持多种后端,如 SQLite、MySQL、Pos…...

【前端】深度解析 JavaScript 中的 new 关键字与构造函数
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 💯前言💯构造函数的核心特性💯new 关键字的执行机制💯实例代码与详细解析代码示例代码逐步解析 💯new 的内部执行模拟执行过程的详细解析 &am…...

2024年华中杯数学建模C题基于光纤传感器的平面曲线重建算法建模解题全过程文档及程序
2024年华中杯数学建模 C题 基于光纤传感器的平面曲线重建算法建模 原题再现 光纤传感技术是伴随着光纤及光通信技术发展起来的一种新型传感器技术。它是以光波为传感信号、光纤为传输载体来感知外界环境中的信号,其基本原理是当外界环境参数发生变化时,…...

使用 `typing_extensions.TypeAlias` 简化类型定义:初学者指南
使用 typing_extensions.TypeAlias 简化类型定义:初学者指南 什么是 TypeAlias?安装 typing_extensions示例代码:如何使用 TypeAlias示例 1:为简单类型定义别名示例 2:为复杂类型定义别名示例 3:结合 Union…...

如何快速批量把 PDF 转为 JPG 或其它常见图像格式?
在某些特定场景下,将 PDF 转换为 JPG 图片格式却具有不可忽视的优势。例如,当我们需要在不支持 PDF 查看的设备或软件中展示文档内容时,JPG 图片能够轻松被识别和打开;此外,对于一些网络分享或社交媒体发布的需求&…...

如何在组织中塑造和强化绩效文化?
在组织中塑造和强化绩效文化是一个系统性的工程。 一、明确绩效目标与期望 设定清晰目标 组织应根据自身战略规划,将长期目标分解为具体、可衡量、可实现、相关联、有时限(SMART)的短期和中期绩效目标。例如,一家连锁餐饮企业的…...

OllyDbg、CE简单介绍
基础知识: 想要破解软件,需要一些基础知识: 文件格式:Windows对应PE、Linux对应ELF、IOS对应Mash-0。文件格式是指操作系统规定的每个段(代码段、数据段、堆、栈)的大小、顺序等信息。 汇编语言࿱…...

Python函数——函数的返回值定义语法
一、引言 在Python中,函数的返回值是其核心功能之一,它使得函数能够将计算结果传递给调用者,进而推动程序的逻辑和功能实现。理解和掌握函数的返回值语法,不仅能够提高代码的模块化和可读性,还能使程序更加高效和灵活…...

【Pandas】pandas isna
Pandas2.2 General Top-level missing data 方法描述isna(obj)用于检测数据中的缺失值isnull(obj)用于检测数据中的缺失值notna(obj)用于检测数据中的非缺失值notnull(obj)用于检测数据中的非缺失值 pandas.isna() pandas.isna() 是 Pandas 库中的一个函数,用于…...

mysql 数据库表的大小
mysql 数据库表的大小 Mysql 查看数据库各个表占用空间 mysql如何查看数据库所有表大小 在MySQL中,要查看数据库所有表的大小,可以使用以下方法: 方法一:使用information_schema数据库 首先,通过命令行或图形界面…...

(6)JS-Clipper2之ClipperOffset
1. 描述 ClipperOffset类封装了对打开路径和关闭路径进行偏移(膨胀/收缩)的过程。 这个类取代了现在已弃用的OffsetPaths函数,该函数不太灵活。可以使用不同的偏移量(增量)多次调用Execute方法,而不必重新分配路径。现在可以在一次操作中对开放和封闭路…...

如何在Ubuntu中利用repo和git地址下载获取imx6ull的BSP
01-设置git的用户名和邮箱 git config --global user.name "suwenhao" git config --global user.email "2487872782qq.com"这里不设置的话后面在第5步的repo配置中还是会要求输入,而且以后进行相关操作都要输入,不妨现在就进行配置…...

Ruby On Rails 笔记5——常用验证下
3.Validation Options 3.1 :allow_nil 当验证值为nil时:allow_nil选项会跳过验证 class Coffee < ApplicationRecordvalidates :size, inclusion: { in: %w(small medium large),message: "%{value} is not a valid size" }, allow_nil: true end irb> Cof…...

JS听到了因果的回响
这是我学习JS的第11天了,,,我现在赶着周末学JS,然后还有二十多天就期末了呵呵呵。。。 图片切换模块 思路分析: 这是实现的代码,建议还是把不同的变量定义出来比较合适: //获取三个盒子// 小盒…...

【高中生讲机器学习】28. 集成学习之 Bagging 随机森林!
创建时间:2024-12-09 首发时间:2024-12-09 最后编辑时间:2024-12-09 作者:Geeker_LStar 嘿嘿,你好呀!我又来啦~~ 前面我们讲完了集成学习之 Boooooosting,这篇我们来看看集成学习的另一个分支…...

硬件设计 | Altium Designer软件PCB规则设置
基于Altium Designer(24.9.1)版本 嘉立创PCB工艺加工能力范围说明-嘉立创PCB打样专业工厂-线路板打样 规则参考-嘉立创 注意事项 1.每次设置完规则参数都要点击应用保存 2.每次创建PCB,都要设置好参数 3.可以设置默认规则,将…...