Mockito 集成 Junit5
在学习Mockito 如何集成 Junit5 之前,先来学习下 Mockito 基础的verify功能。
Maven依赖
本篇博客代码的Maven依赖如下,源码地址
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>6.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!-- utils -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- test scoped -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.22.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Mockito Verify
Mockito Verify 方法经常被用于检查校验对象是否发生特定行为 - 即方法. 开发者可在测试方法代码的末尾使用verify方法, 确定是否调用了指定的方法。Mockito Verify主要从以下几个方面对调用行为进行判断
- 校验是否调用指定方法
- 校验没有调用其他方法
- 校验方法调用次数
- 校验调用方法顺序
- 校验调用方法的参数
接下来详细学习下 Mockito Verify 相关的API
验证简单调用
- 验证是否调用指定方法
@Test
void test1(){
List<String> list = mock(ArrayList.class);
list.size();
// 验证是否调用size 方法
verify(list).size();
}
-
验证没有调用其他方法
@Test void test2(){ List<String> list = mock(ArrayList.class); // 验证list 对象没发生额外调用 verifyNoMoreInteractions(list); }
-
验证从没调用过指定方法
@Test void test3(){ List<String> list = mock(ArrayList.class); list.size(); //从未调用过clear 方法 verify(list,never()).clear(); }
验证调用次数
-
验证仅调用一次
@Test void test0(){ List<String> list = mock(ArrayList.class); list.size(); // times(1), only(),atLeast(1) 都能实现一次调用的验证 //verify(list,times(1)).size(); //verify(list,only()).size(); verify(list,atLeast(1)).size(); }
-
验证至少调用次数
@Test void test0(){ List<String> list = mock(ArrayList.class); list.size(); // 最少一次调用验证 verify(list,atLeastOnce()).size(); }
-
验证最多调用次数
@Test void test0(){ List<String> list = mock(ArrayList.class); list.size(); //最多一次 verify(list,atMostOnce()).size(); // 最多10次 verify(list,atMost(10)).size(); }
验证调用顺序
@Test
void test3(){
List<String> list = mock(ArrayList.class);
list.size();
list.add("hello");
list.clear();
InOrder inOrder = Mockito.inOrder(list);
inOrder.verify(list).size();
inOrder.verify(list).add(anyString());
inOrder.verify(list).clear();
}
验证调用参数
-
验证指定参数
@Test void test6(){ List<String> list = mock(ArrayList.class); list.add("hello"); verify(list).add("hello"); // 以下两种 验证参数类型 verify(list).add(anyString()); verify(list).add(any(String.class)); }
-
验证捕获参数
@Test void test7(){ List<String> list = mock(ArrayList.class); list.addAll(Lists.<String> newArrayList("someElement")); ArgumentCaptor<List<String>> captor = ArgumentCaptor.forClass(List.class); verify(list).addAll(captor.capture()); // 捕获参数 并验证是否包含特定值 final List<String> value = captor.getValue(); assertThat(value).contains("someElement"); }
集成Junit5
有了之前的基础后,接下来来学习下Mockito 如何集成 Junit5,了解Junit5如何对Mockito进行扩展。大致的流程:
- 首先先创建Junit5扩展,该扩展使用@mock自动创建mock对象,模拟任何对象属性或者方法
- 然后在Junit5测试类中使用Mockito扩展
应用场景
假设现在有一个项目,团队需要基于SpringBoot MVC的架构模式进行开发。由于业务足够复杂,分工细致。你被要求开发 service层的业务逻辑,Repository数据操作层由其他团队负责,双方根据约定好的接口进行协同开发。
由于你的专业技术能力过硬,开发效率较高,因此已经完成了开发工作。但是其他团队Repository没有完成,只提供了对应的接口。现在您需要对整理流程进行集成测试,此时Mock排上用场。
基础代码
-
User 实体类
@Data @ToString @NoArgsConstructor public class User { private Integer id; private String name; private int age; public User(String name,int age){ this.name = name; this.age = age; } }
-
service接口
import com.andy.spring.junit5.mockito.User; public interface UserService { User register(User user); }
-
service实现类
import com.andy.spring.junit5.mockito.User; import com.andy.spring.junit5.mockito.repository.UserRepository; import com.andy.spring.junit5.mockito.service.UserService; public class DefaultUserService implements UserService { private UserRepository userRepository; public DefaultUserService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public User register(User user) { validate(user); User insertedUser = userRepository.insert(user); return insertedUser; } private void validate(User user) { if(user.getName() == null) { throw new RuntimeException("用户名称不能为空"); } if(userRepository.isUsernameAlreadyExists(user.getName())) { throw new RuntimeException("用户名称已存在"); } } }
-
Repository 接口定义
import com.andy.spring.junit5.mockito.User; public interface UserRepository { User insert(User user); boolean isUsernameAlreadyExists(String userName); }
验证测试
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
private UserService userService;
@Mock
private UserRepository userRepository;
private User user;
//每次运行测试方法都进行初始化
@BeforeEach
void init(){
userService = new DefaultUserService(userRepository);
//输入任意字符串 参数校验都返回false
lenient().when(userRepository.isUsernameAlreadyExists(any(String.class)))
.thenReturn(false);
}
@Test
void test1(){
user = new User("kobe",40);
when(userRepository.insert(any(User.class))).then(new Answer<User>() {
int sequence = 1;
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
//模拟插入db后,对ID主键进行赋值
User user = (User) invocation.getArgument(0);
user.setId(sequence++);
return user;
}
});
userService = new DefaultUserService(userRepository);
// When
User insertedUser = userService.register(user);
System.out.println("insertedUser : " + insertedUser);
// Then 验证是否调用 insert 方法 参数为user
verify(userRepository).insert(user);
assertNotNull(user.getId());
}
}
以上代码很容易理解,值得注意的是需要在class类上加上**@ExtendWith(MockitoExtension.class)**注解。测试截图