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…...
十六,Spring Boot 整合 Druid 以及使用 Druid 监控功能
十六,Spring Boot 整合 Druid 以及使用 Druid 监控功能 文章目录 十六,Spring Boot 整合 Druid 以及使用 Druid 监控功能1. Druid 的基本介绍2. 准备工作:3. Druid 监控功能 3.1 Druid 监控功能 —— Web 关联监控3.2 Druid 监控功能 —— …...
Python办公—DataMatrix二维条码制作
目录 专栏导读1、库的介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…...
Linux:软硬链接
目录 一、概念 软链接 硬链接 二、原理 硬链接 软链接 三、使用场景 硬链接 软链接 一、概念 软链接 在当前目录下,有一个普通文件a.txt。 ln -s a.txt a_soft.link结论: 软链接是一个文件。 观察inode_id,发现软链接有着独立…...
[笔记] Windows 上 Git 安装详细教程:从零开始,附带每个选项解析
Git 是目前最流行的分布式版本控制系统之一,广泛应用于软件开发和项目管理中。对于 Windows 用户来说,正确安装和配置 Git 是开始使用 Git 的第一步。本文提供一份详细的指南,帮助你在 Windows 系统上顺利安装 Git,并解释每个安装…...
23种设计模式之策略模式
目录 1. 简介2. 代码2.1 Strategy (策略接口)2.2 AddStrategy (具体策略类)2.3 SubStrategy (具体策略类)2.4 MultiplyStrategy (具体策略类)2.5 Operation (上下文类&am…...
总篇:Python3+Request+Pytest+Allure+Jenkins接口自动化框架设计思路
1、技术选型 Python3 Python 是一种广泛使用的高级编程语言,具有简洁、易读、易维护的特点。 Python 拥有丰富的第三方库,可以方便地进行接口测试的开发。 Request Request 是一个强大的 HTTP 库,用于发送 HTTP 请求和处理响应。 Request 支持多种 HTTP 方法,如 GET、P…...
【QML】release版本bug,信号的参数无法获取
1. 现象 问题 QML程序在debug编译模式下程序可以正常运行,但是release版本下报错:ReferenceError: para is not defined版本 Qt creator 10.0.2Qt_5_15_2_MinGW 平台 win 10 2. 解决方法 暂时没有找到好的解决办法,只能规避规避方法 //问…...
Javaweb 前端 ajax
作用:和后端交互 script 是 js axios(这里是函数的调用方式){封装的是对象} {}是对象 案例 。then的含义,请求后端之后,后端把数据放在回调 点了清空之后,还要查询全部 await等待请求执行完之后,接收这个结果 代码…...
汽车EEA架构:发展历程
1.发展历程的基本逻辑 汽车电子电气的发展历程中,其使用的基本逻辑是IPO(Input-Processing-Output)模型,如下图1所示: 图 1 那什么是IPO模型了?我们从控制器的原理入手解释IPO模型,控制器的主要用途如下: 根据给定的逻…...
几个Linux系统安装体验: 统信服务器系统
本文介绍统信服务器系统(UOS)的安装。 下载 下载地址: https://www.chinauos.com/resource/download-server 本文下载的文件名称为uos-server-20-1070e-amd64.iso。 安装 本次实践仅是做测试体验,因此在pc上使用虚拟机vmware…...
用二维图像渲染3D场景视频
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
ChatGPT 最新推出的 Pro 订阅计划,具备哪些能力 ?
OpenAI 最近推出了 ChatGPT Pro,这是一个每月收费 200 美元的高级订阅计划,旨在为用户提供对 OpenAI 最先进模型和功能的高级访问。 以下是 ChatGPT Pro 的主要功能和能力: 高级模型访问: o1 模型:包括 o1 和 o1 Pro…...
如何在 IntelliJ IDEA 中为 Spring Boot 应用实现热部署
文章目录 1. 引言2. 准备工作3. 添加必要的依赖4. 配置 IntelliJ IDEA4.1 启用自动编译4.2 开启热部署策略 5. 测试热部署6. 高级技巧7. 注意事项8. 总结 随着现代开发工具的进步,开发者们越来越重视提高生产力的特性。对于 Java 开发者来说,能够在不重启…...
NLP与LLM的工程化实践与学习思考 - 写在开头
NLP与LLM的工程化实践与学习思考[24年半年工作总结] - 写在开头 0 开头的开头 0 开头的开头 24年因为一些工作原因,短暂在NLP领域遨游了半年。这半年对我的影响蛮大,一来是因为此前从没接触过这个方向学到新东西挺开心的,二来是在工程化实践…...
Redis(一)
Redis 基础 什么是 Redis? Redis (REmote DIctionary Server)是一个基于 C 语言开发的开源 NoSQL 数据库(BSD 许可)。与传统数据库不同的是,Redis 的数据是保存在内存中的(内存数据库…...
RocketMq源码-broker(五)
一、RocketMq存储设计 RocketMQ 主要存储的文件包括Commitlog 文件、ConsumeQueue 文件、IndexFile。RocketMQ 将所有主题的消息存储在同一文件,确保消息发送时顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐量。 但由于一般的消息中间件是基于消…...
【Linux】文件描述符fd
1.前置预备 文件 内容 属性访问文件之前,都必须先打开他 #include<stdio.h> int main() { FILE* fpfopen("log.txt","w"); if(fpNULL) { perror("fopen"); return 1; } fclose(fp); return 0…...
mysql之事务
MySQL的事务隔离特性指的是多个并发事务之间相互隔离的程度,以保证数据的一致性和并发性。MySQL支持四个隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repe…...
python插入mysql数据
# 插入与上一篇变化不大,只是需要进行确认操作. 增加确认操作的方法有两种(假设类对象为a): 1.在连接时传入一个参数:autocommitTrue aConnection( host"localhost", port3306, user"root", password"自己的密码…...
C语言面试题/笔试题/高频面试题
一、C: 1.static和const的作用优缺点 限制作用域: static声明中使用全局变量、函数 ,仅当前文件内可用,其他文件不能引用 static修饰的局部变量只能在本函数中使用. 延长生命周期: static修饰的变量生命周期为整个程序 存放位置&a…...
rust websocket Echo server高性能服务器开发
最近在学习websocket时,一直没有发现好的websocket server工具来调试,于是就自己做了一个websocket server用来学习和调试。因为rust性能遥遥领先,所以就采用了rust来搭建服务器。废话不多说直接上代码main.rs: use tokio::net::TcpListener; use tokio_tungstenite::tung…...
Docker打包SpringBoot项目
一、项目打成jar包 在进行docker打包之前,先确定一下,项目能够正常的打成JAR包,并且启动之后能够正常的访问。这一步看似是可有可无,但是能避免后期的一些无厘头问题。 二、Dockerfile 项目打包成功之后,需要编写Doc…...
ViT学习笔记(二) Patch+Position Embedding阶段的详细推演与理解
我认为讲得最好的一个文章:Vision Transformer详解-CSDN博客 有很多文章,自己并没有完全正确理解。 我的笔记,以ViT的标准应用为例: • 输入图像:输入图像的尺寸是224x224,且是RGB图像,因此输…...
Elasticsearch 单节点安全配置与用户认证
Elasticsearch 单节点安全配置与用户认证 安全扫描时发现了一个高危漏洞:Elasticsearch 未授权访问 。在使用 Elasticsearch 构建搜索引擎或处理大规模数据时,需要启用基本的安全功能来防止未经授权的访问。本文将通过简单的配置步骤,为单节…...
【PHP项目实战】活动报名系统
目录 项目介绍 开发语言 后端 前端 项目截图(部分) 首页 列表 详情 个人中心 后台管理 项目演示 项目介绍 本项目是一款基于手机浏览器的活动报名系统。它提供了一个方便快捷的活动报名解决方案,无需下载和安装任何APP,…...
ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤
一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式: (1)预先加载:直接在查询主体时就把对应的依赖实体查出来(作为初始查询的一部分) (2)显式加载:使用代码指示稍后显式的从…...
【RK3562J开发笔记】MCP2518FD外部CAN-FD控制器的调试方法
“SPI转CAN-FD”是嵌入式开发领域的常用方法,它极大地促进了不同通信接口之间的无缝连接,并显著降低了系统设计的复杂性。飞凌嵌入式依托瑞芯微RK3562J处理器打造的OK3562J-C开发板因为内置了SPI转CAN-FD驱动,从而原生支持这一功能。该开发板…...
docker安装Emqx并使用自签名证书开启 SSL/TLS 连接
docker安装Emqx并使用自签名证书开启 SSL/TLS 连接 一、获取自签名证书1、创建openssl.cnf文件2、生成证书自签名证书 二、docker安装EMQX1、初始化目录2、加载镜像文件并挂载相应的文件目录3、启动docker容器4、EMQX加载自签名证书 三、客户端MQTTX连接测试四、Springboot整合…...
AI驱动的低代码平台:解密背后的算法与架构创新
引言 在如今的数字化浪潮中,企业对软件的需求正以前所未有的速度增长。传统的开发方式由于开发周期长、成本高,已逐渐无法满足市场的快速变化。而低代码平台的出现,使得开发者和业务人员能够以极简的方式快速构建应用。然而,随着企…...
ruoyi的excel批量导入
最简单方式 若依的官方文档提供了教程,可以按照起前后端的教学,进行代码编写 前段 组件 <!-- 导入对话框 --><el-dialogtitle"导入数据"v-model"openImport"width"500px"append-to-body><el-uploadref&quo…...
大数据-244 离线数仓 - 电商核心交易 ODS层 数据库结构 数据加载 DataX
点一下关注吧!!!非常感谢!!持续更新!!! Java篇开始了! 目前开始更新 MyBatis,一起深入浅出! 目前已经更新到了: Hadoop࿰…...
Spring Security
一.权限控制 1.1 认证和授权概念 问题1:在生产环境下我们如果不登录后台系统就可以完成这 些功能操作吗? 答案显然是否定的,要操作这些功能必须首先登录到系统才可 以。 问题2:是不是所有用户,只要登录成功就都可以操…...
OpenAI 正式发布 o1 完整版
OpenAI 在 o1 模型完整版,该模型相较于之前的 o1-preview 版本在智能能力上有所提升,特别是在编程能力方面,并且能够根据问题的难度智能调节响应速度。此外还新增了图像识别功能,但目前仍然不支持网页浏览、文件上传等功能 o1 模…...
Ubuntu22.04搭建LAMP环境(linux服务器学习笔记)
目录 引言: 一、系统更新 二、安装搭建Apache2 1.你可以通过以下命令安装它: 2.查看Apache2版本 3.查看Apache2运行状态 4.浏览器访问 三、安装搭建MySQL 1.安装MySQL 2.查看MySQL 版本 3.安全配置MySQL 3.1是否设置密码?(按y|Y表…...
C#与PLC通讯时,数据读取和写入浮点数,字节转换问题(ModbusTCP)
在与PLC进行通讯时,会发现一个问题,浮点数1.2接收过来后,居然变成了两个16位的整数。 经过一系列的分析,这是因为在PLC存储浮点数时32位,我们接收过来的数据会变成两个16位的高低字节,而且我们进行下发数据…...
synchronized的特性
1.互斥 对于synchronized修饰的方法及代码块不同线程想同时进行访问就会互斥。 就比如synchronized修饰代码块时,一个线程进入该代码块就会进行“加锁”。 退出代码块时会进行“解锁”。 当其他线程想要访问被加锁的代码块时,就会阻塞等待。 阻塞等待…...
NLP与LLM的工程化实践与学习思考 - 说说知识图谱
NLP与LLM的工程化实践与学习思考[24年半年工作总结] - 说说知识图谱 0 真的就是先说说1 为什么知识图谱什么是知识图谱?基于图的数据结构?基于数据结构的图?知识图谱的技术要点两个技术维度:知识、图七个技术要点:表示…...
php 系统函数 记录
PHP intval() 函数 PHP函数介绍—array_key_exists(): 检查数组中是否存在特定键名 如何使用PHP中的parse_url函数解析URL PHP is_array()函数详解,PHP判断是否为数组 PHP函数介绍:in_array()函数 strpos定义和用法 strpos() 函数查找字符串在另一字符串…...
游戏引擎学习第38天
仓库: https://gitee.com/mrxiao_com/2d_game 回顾上次的内容。 我们之前讨论了将精灵放在屏幕上,但颜色错误的问题。问题最终查明是因为使用了一个调整工具,导致文件的字节顺序发生了变化。重新运行“image magic”工具对一些大图像进行重新处理后&am…...
Android 15 行为变更:所有应用
Android 15 平台包含一些可能会影响您的应用的行为变更。以下行为变更将影响在 Android 15 上运行的所有应用,无论采用哪种 targetSdkVersion 都不例外。您应该测试您的应用,然后根据需要进行修改,以适当地支持这些变更。 此外,请…...
基于pytorch的深度学习基础4——损失函数和优化器
四.损失函数和优化器 4.1 均值初始化 为减轻梯度消失和梯度爆炸,选择合适的权重初值。 十种初始化方法 Initialization Methods 1. Xavie r均匀分布 2. Xavie r正态分布 4. Kaiming正态分布 5. 均匀分布 6. 正态分布 7. 常数分布 8. 正交矩阵初…...
《Clustering Propagation for Universal Medical Image Segmentation》CVPR2024
摘要 这篇论文介绍了S2VNet,这是一个用于医学图像分割的通用框架,它通过切片到体积的传播(Slice-to-Volume propagation)来统一自动(AMIS)和交互式(IMIS)医学图像分割任务。S2VNet利…...
Webpack Source Map 配置详解与优化策略
前言 Source Map 是前端开发和调试中的核心工具之一,它可以显著提高我们在代码调试和错误追踪方面的效率。随着 JavaScript 应用越来越复杂,代码打包和优化成为必然,而这一过程会使得调试变得异常困难。Source Map 的出现,为我们…...
el-tree组件刷新指定id的节点数据
示例 封装一个可以刷新多个指定id的节点数据。 <template><el-tree ref"treeRef"></el-tree> </template><script lang"ts" setup> const treeRef ref()function refreshTreeById(nodeIds: number[]) {nodeIds.forEach((…...
深入 Java 基础 XML:高级特性与最佳实践
在上一篇文章中,我们对 Java 基础 XML 有了一个初步的认识,了解了 XML 的基本结构以及在 Java 中常见的解析方式。今天,我们将进一步深入探讨 Java 与 XML 的结合,包括一些高级特性和最佳实践。 一、XML 命名空间 在复杂的 XML …...
aws(学习笔记第十六课) 使用负载均衡器(ELB)解耦webserver以及输出ELB的日志到S3
aws(学习笔记第十六课) 使用负载均衡器(ELB)以及输出ELB的日志到S3 学习内容: 使用负载均衡器(ELB)解耦web server输出ELB的日志到S3 1. 使用负载均衡器(ELB) 全体架构 使用ELB(Elastic Load Balancer)能够解耦外部internet访问和web server之间的耦合,…...
Ubuntu与Centos系统有何区别?
Ubuntu和CentOS都是基于Linux内核的操作系统,但它们在设计理念、使用场景和技术实现上有显著的区别。以下是详细的对比: 1. 基础和发行版本 Ubuntu: 基于Debian,使用.deb包管理系统。包含两个主要版本: LTSÿ…...
【OpenDRIVE_Python】使用python脚本读取txt指定内容,输出OpenDRIVE数据中对应的信息
示例代码说明: 读取txt指定内容如地物id,输出OpenDRIVE数据中的对应地物id和名称name信息为xml文件 import xml.dom.minidom from xml.dom.minidom import parse from xml.dom import Node import sys import os # 读取OpenDRIVE文件路径 xml_filepath…...
Qt入门8——Qt文件
1. Qt文件概述 文件操作是应用程序必不可少的部分。Qt作为⼀个通用开发库,提供了跨平台的文件操作能力。Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等。 2. 输入输出设备类 在…...