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 提供的注解,用于生成模拟对象,是创建了一个新的对象。这里的 userDaowebServiceClient 是通过 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 对象,并将 userDaowebServiceClient 作为依赖注入到这个新的对象中。
  • 与 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 的测试方法
  1. 使用 @MockBean:用来替换 Spring 容器中的 bean,模拟它的行为。
  2. 使用 @Autowired:注入 Spring 容器中实际的服务(如 UserService)。
  3. 使用 @SpringBootTest:启动 Spring Boot 的测试上下文。
常用注解:
@MockBean:

@MockBeanSpring Boot 提供的注解,用于创建一个 Mockito 模拟对象,并将它替换到 Spring 上下文中。userDaowebServiceClient 是通过 @MockBean 模拟的对象,而不是真实的对象。这些模拟对象将替换 Spring 容器中的相应 bean,然后可以通过@Autowird自动注入被依赖类中。

@SpyBean:

@SpyBeanSpring Boot 提供的一个注解,专门用于 部分模拟(Partial Mocking) Spring 容器中的 Bean。它的作用是创建一个部分模拟的对象,部分调用真实方法,部分进行模拟(Mock)行为。相比于 Mockito 提供的 @Spy@SpyBean 更加集成到 Spring 环境中,并且允许你将某个 Spring 容器中的 Bean 替换为部分模拟对象。

@SpyBean 的工作原理

  1. 部分模拟@SpyBean 允许你对 Spring 容器中的现有 Bean 进行部分模拟。这意味着模拟的 Bean 会保留其大部分原始行为,只有你明确模拟的部分会改变。
  2. 注入到 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 进行测试的一般流程可以分为以下几个步骤:

  1. 设置测试环境:在单元测试中,通过 Mockito 的注解或者方法来创建模拟对象(Mock)。模拟对象是用于替代真实的依赖,以便控制和测试不同的场景。
  2. 定义模拟行为:使用 Mockito 的方法定义模拟对象的方法行为。通常通过 when(...).thenReturn(...) 来模拟返回特定值,或者使用 doThrow() 来模拟异常抛出。
  3. 执行测试代码:编写业务逻辑代码,将模拟对象注入依赖进行测试并断言结果。
  4. 验证行为:使用 verify() 验证方法调用、参数、调用次数等。

1. 准备测试环境

在测试类中准备需要的模拟对象和被测对象。可以通过 @Mock@InjectMocks 注解或者 Mockito.mock() 方法手动创建模拟对象。对于 Spring 项目,还可以使用 @MockBean 来替代 Spring 容器中的 Bean。

2、定义模拟行为:

  1. 使用 when(...).thenReturn(...) 来模拟方法返回值。
  2. 使用 doReturn(...).when(...) 来避免方法的真实调用。
  3. 使用 thenThrow(...)doThrow(...).when(...) 来模拟异常。
  4. 使用 thenAnswer(...) 来处理复杂的动态行为。
  5. 使用 doNothing() 来处理 void 方法。
1. 使用 when(...).thenReturn(...) 来模拟方法返回值

这是最常用的方式。适用于模拟方法调用后需要返回某个特定值的情况。

示例:模拟 UserDaogetUserById() 方法在调用时返回特定的 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(...)
  1. 处理 void 方法when(...).thenReturn(...) 不能用于模拟 void 方法,因为 void 方法没有返回值。这时你需要使用 doReturn()doThrow() 来模拟 void 方法的行为。

    示例

    doNothing().when(mockObject).someVoidMethod();
    
  2. 部分模拟(@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) 则不会调用真实方法。

  3. 方法抛出异常的场景:某些情况下,方法在实际调用时会抛出异常。如果你不希望方法抛出异常(例如,你只关心返回结果的模拟),使用 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() 的主要特点:
  1. 基于输入参数动态响应:你可以根据方法的输入参数来生成不同的返回结果。
  2. 执行自定义逻辑:它允许你在模拟方法中执行特定的自定义逻辑,而不仅仅是返回一个固定值。
  3. 复杂行为模拟:适用于更复杂的业务场景,比如多个条件组合下的不同返回值,或者需要根据传入参数执行计算等。
thenAnswer() 使用方法

thenAnswer() 接受一个 Answer 接口的实现作为参数。Answer 接口定义了一个 answer(InvocationOnMock invocation) 方法,该方法会在模拟方法被调用时执行。你可以通过这个方法来访问方法的调用信息(包括传入的参数),并根据需要自定义返回结果或逻辑。

//Answer 接口定义
public interface Answer<T> {T answer(InvocationOnMock invocation) throws Throwable;
}
  • InvocationOnMock:提供了对当前调用的所有信息,包括参数、调用的 mock 对象等。
  • answer():在方法被调用时触发,用于自定义返回值或执行逻辑。

InvocationOnMock 接口提供了几个常用的方法,允许你访问模拟方法调用的详细信息:

  1. getMock():返回当前被调用的模拟对象。
  2. getMethod():返回被调用的 Method 对象。
  3. getArguments():返回方法的所有传递参数的数组。
  4. getArgument(int index):返回指定索引位置的单个参数。
  5. getArgument(int index, Class<T> clazz):返回指定索引的参数并强制转换为指定类型。
  6. getArgumentsCount():返回传递的参数数量。
  7. callRealMethod():调用被模拟方法的真实实现(常用于部分模拟 @Spy)。
示例:根据传入的参数动态返回不同结果

假设有一个 UserDaogetUserById 方法,你希望根据传入的用户 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());
}

解释

  1. thenAnswer(new Answer<User>() {...}):在 getUserById 方法被调用时,触发 Answer 接口的 answer() 方法来生成返回值。
  2. invocation.getArgument(0):获取方法调用时的第一个参数,这里是传入的 userId
  3. 动态生成返回结果:根据传入的 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());
}

解释

  1. 根据传入参数抛出异常:当传入的 userId 小于 0 时,抛出 IllegalArgumentException,否则正常返回用户对象。
  2. 测试不同情况:我们通过 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());
}

解释

  1. callCount:通过一个计数器 callCount 来记录方法的调用次数。
  2. 根据调用次数返回不同的结果:第一次调用返回一个结果,后续调用返回不同的结果。
thenAnswer() 的优势
  • 灵活性高thenAnswer() 允许你在方法被调用时基于参数或其他条件生成返回值或抛出异常,比简单的 thenReturn() 更加灵活。
  • 动态行为模拟:可以根据方法的参数、调用次数、甚至外部状态来决定模拟的行为,适用于复杂的测试场景。
  • 测试特定逻辑:它可以帮助你测试依赖复杂逻辑的代码片段,尤其是在简单的 thenReturn()thenThrow() 无法满足需求时。

3. 执行测试代码并断言

编写测试代码,调用被测类中的方法。由于被测类的依赖已经被模拟对象替换,所以你可以专注于测试当前方法的逻辑,而不必担心真实依赖带来的副作用。在执行完测试代码后,你可以通过 JUnit 的断言 来检查测试结果是否符合预期。常用的断言包括 assertEquals()assertTrue()assertNull() 等。

JUnit 常用的断言方法

在 JUnit 5 中,所有断言方法都位于 org.junit.jupiter.api.Assertions 类中。常见的断言包括:

  1. assertEquals(expected, actual):断言两个值是否相等。
  2. assertNotEquals(unexpected, actual):断言两个值是否不相等。
  3. assertTrue(condition):断言条件为 true
  4. assertFalse(condition):断言条件为 false
  5. assertNull(object):断言对象是否为 null
  6. assertNotNull(object):断言对象是否不为 null
  7. assertSame(expected, actual):断言两个对象引用是否指向同一个对象。
  8. assertNotSame(unexpected, actual):断言两个对象引用是否不指向同一个对象。
  9. assertThrows(expectedType, executable):断言执行代码时抛出特定类型的异常。
  10. 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、验证行为:

验证行为的常用方法
  1. verify():验证某个模拟对象的方法是否被调用。
  2. verifyNoMoreInteractions():验证某个模拟对象的方法在指定的调用之外,没有其他额外的调用。
  3. verifyZeroInteractions() / verifyNoInteractions():验证某个模拟对象从未被调用。
  4. InOrder:验证多个方法调用的顺序。
  5. times():验证某个方法被调用的次数。
  6. never():验证某个方法从未被调用。
  7. 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():验证 saveUsernotifyUser 方法是否按照指定的顺序被调用。

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);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/484705.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

车机端同步outlook日历

最近在开发一个车机上的日历助手&#xff0c;其中一个需求就是要实现手机端日历和车机端日历数据的同步。然而这种需求似乎没办法实现&#xff0c;毕竟手机日历是手机厂商自己带的系统应用&#xff0c;根本不能和车机端实现数据同步的。 那么只能去其他公共的平台寻求一些机会&…

多人聊天室 NIO模型实现

NIO编程模型 Selector监听客户端不同的zhuangtai不同客户端触发不同的状态后&#xff0c;交由相应的handles处理Selector和对应的处理handles都是在同一线程上实现的 I/O多路复用 在Java中&#xff0c;I/O多路复用是一种技术&#xff0c;它允许单个线程处理多个输入/输出&…

电商产品自动化测试实战—解锁高效测试新技能

在这个数字化时代&#xff0c;电子商务行业的竞争愈发激烈&#xff0c;产品品质和稳定性成为了企业赢得市场的关键。而高质量的测试工作&#xff0c;正是确保产品品质和稳定性的重要保障。为此&#xff0c;我们特别推出了一场电商产品自动化测试实战公开课&#xff0c;旨在帮助…

【JavaWeb后端学习笔记】Mybatis基础操作以及动态SQL(增、删、改、查)

Mybatis 0、环境准备0.1 准备数据库表emp&#xff1b;0.2 准备SpringBoot工程0.3 配置文件中引入数据库连接信息0.4 创建对应的实体类0.5 准备Mapper接口 1、MyBatis基础操作1.1 删除1.2 新增&#xff08;主键返回&#xff09;1.3 更新1.4 查询&#xff08;解决字段名与类属性名…

SpringBoot该怎么使用Neo4j - 优化篇

文章目录 前言实体工具使用 前言 上一篇中&#xff0c;我们的Cypher都用的是字符串&#xff0c;字符串拼接简单&#xff0c;但存在写错的风险&#xff0c;对于一些比较懒的开发者&#xff0c;甚至觉得之间写字符串还更自在快速&#xff0c;也确实&#xff0c;但如果在后期需要…

如何用AI生成胶片风格的场景图 - 实用教程

如何用AI生成胶片风格的场景图 - 实用教程 在这个教程中,我们将介绍如何使用Recraft AI生成复古胶片风格的场景图。通过简单的步骤,你就能创建出独特的复古风格图片。 成功案例展示 小红书爆火作品 11月22日,小红书博主"四月崔aprilchui"发布胶片风格的场景图…

在M3上面搭建一套lnmp环境

下载docker-desktop 官网下载docker-desktop 切换镜像源 {"builder": {"gc": {"defaultKeepStorage": "20GB","enabled": true}},"experimental": false,"registry-mirrors": ["https://docke…

思特奇亮相2024数字科技生态大会,以“智”谋新共赢AI新时代

12月3-5日,2024数字科技生态大会在广州琶洲广交会展馆D区盛大举行。大会以“AI赋能 共筑数字新生态”为主题,汇聚行业领军企业、创新型科技公司以及众多专家学者,共探数字经济时代未来发展新机遇。 作为中国电信长期重要的生态伙伴,思特奇受邀参会并亮相18.2号馆天翼AI展区,重点…

【全网最新】若依管理系统基于SpringBoot的前后端分离版本开发环境配置

目录 提前准备&#xff1a; 下载源代码 设置依赖 设置后台连接信息 运行后台 运行前端 安装npm依赖 启动前端 登录网页客户端 提前准备&#xff1a; 1、安装mysql 5以上就可以。 2、安装redis. 3、安装npm npm下载地址&#xff1a;https://nodejs.org/dist/v22.12…

远程游戏新体验!

在这个数字化的时代&#xff0c;游戏已经不仅限于家里的电视或书房的电脑了。远程游戏&#xff0c;也就是通过远程控制软件在不同地点操作游戏设备&#xff0c;给玩家带来了前所未有的自由和灵活性。RayLink远程控制软件&#xff0c;凭借其出色的性能和专为游戏设计的功能&…

【人工智能】Transformers之Pipeline(二十八):视觉问答(visual-question-answering)

​​​​​​​ 目录 一、引言 二、视觉问答&#xff08;visual-question-answering&#xff09; 2.1 概述 2.2 dandelin/ViLT 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 2.3.3 pipeline对象返回参数 2.4 pipeline实战 2.5 模型…

qt QPrinter详解

1、概述 QPrinter类是Qt框架中用于打印输出的绘图设备。它表示打印出来的一系列页面&#xff0c;并提供了一组附加功能来管理特定于设备的特性&#xff0c;比如方向和分辨率。QPrinter可以生成PDF文档&#xff0c;也可以将内容发送到打印机进行实际打印。它继承自QPagedPaintD…

AI开发: 知识图谱的初识,学会制作知识图谱- Python 机器学习

一、知识图谱的概念 知识图谱是一个通过图结构来表示和组织知识的工具&#xff0c;它将事物、概念和它们之间的关系以图的形式呈现出来&#xff0c;图中的节点代表实体&#xff08;比如人物、地点、事件等&#xff09;&#xff0c;而边代表这些实体之间的各种关系&#xff08;…

移动端登录注册界面样式,简洁切换

非常简洁的登录、注册界面模板&#xff0c;使用uni-app编写&#xff0c;直接复制粘贴即可&#xff0c;无任何引用&#xff0c;全部公开。 废话不多说&#xff0c;代码如下&#xff1a; login.vue文件 <template><view class"content"><view class&quo…

RTMP如何实现毫秒级延迟体验?

技术背景 在我们大多数音视频行业从业者的认知里&#xff0c;RTMP播放器的延迟通常可以做到2到3秒。实际上&#xff0c;在较为理想的网络环境和优化良好的系统设置下&#xff0c;RTMP播放器一样可以做到几百毫秒的延迟水平。今天就影响RTMP播放延迟的一些因素&#xff0c;做个…

Oracle数据库 用户管理模式下的冷备份与热备份

1. 用户管理模式下的冷备份 1.1. 通过数据库相关视图查询 查实例 select instance_name,version,status,archiver,database_status from v$instance; 查数据库 select dbid,name,log_mode from v$database; 查数据文件状态 select file_name,tablespace_name,status,o…

【k8s 深入学习之 event 聚合】event count累记聚合(采用 Patch),Message 聚合形成聚合 event(采用Create)

参考 15.深入k8s:Event事件处理及其源码分析 - luozhiyun - 博客园event 模块总览 EventRecorder:是事件生成者,k8s组件通过调用它的方法来生成事件;EventBroadcaster:事件广播器,负责消费EventRecorder产生的事件,然后分发给broadcasterWatcher;broadcasterWatcher:用…

浙江工业大学《2024年828自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《浙江工业大学828自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

AI开发:用模型来识别手写数字的完整教程含源码 - Python 机器学习

今天一起来学习scikit-learn 。 scikit-learn 是一个强大的 Python 机器学习库&#xff0c;提供多种分类、回归、聚类算法&#xff0c;适用于从数据预处理到模型评估的全流程。它支持简单一致的 API&#xff0c;适合快速构建和测试模型。 官方地址在这里&#xff0c;记得Mark…

【Docker】创建Docker并部署Web站点

要在服务器上创建Docker容器&#xff0c;并在其中部署站点&#xff0c;你可以按照以下步骤操作。我们将以Flask应用为例来说明如何完成这一过程。 1. 准备工作 确保你的服务器已经安装了Docker。如果没有&#xff0c;请根据官方文档安装&#xff1a; Docker 安装指南 2. 创…