在学习Mockito 如何集成 Junit5 之前,先来学习下 Mockito 基础的verify功能。
本篇博客代码的Maven依赖如下,源码地址
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>6.0.2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>6.0.2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.0.2version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.12.0version>
dependency>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-junit-jupiterartifactId>
<version>4.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>31.1-jreversion>
dependency>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-coreartifactId>
<version>4.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.assertjgroupId>
<artifactId>assertj-coreartifactId>
<version>3.22.0version>
<scope>testscope>
dependency>
dependencies>
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");
}
有了之前的基础后,接下来来学习下Mockito 如何集成 Junit5,了解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)**注解。测试截图