Mockito新手使用教程

Mockito新手使用教程

背景

实在惭愧,干开发这么久了,从来没在单元测试用过mockito,倒不是没写单元测试的意思,只是用都是@SpringBootTest这类型的集成测试,有时候服务后台需要做mock接口,也是用的WireMock。第一次接触mockito,有点懵不清楚该怎么用,找了几个文章看了一下,都是教你那几个注解的含义,没有介绍使用的场景。

新手一定都有的疑问

  1. @Mock修饰的类型,他的调用方法都是虚拟的,并不是真正执行的,意义何在?
  2. 我怎么确保整个流程是正常通畅的?
  3. 有些流程确实要执行真正调用的,该怎么办?

概念

Mockito的主要功能和特点包括:

  1. 创建模拟对象:使用Mockito.mock(Class<T> classToMock)方法创建一个模拟对象。
  2. 设定行为:使用when(mock.method()).thenReturn(value)来设定当调用某个方法时返回特定的值。
  3. 验证行为:使用verify(mock).method()来验证某个方法是否被调用。
  4. 参数匹配器:使用ArgumentMatchers类来匹配参数,以便在设定行为和验证时更灵活。
  5. 注解支持:使用@Mock@InjectMocks注解来简化模拟对象的创建和注入。

mockito常用的注解

1. @Mock

用于创建和注入模拟对象。

@Mock
private SomeService someService;
  • @Mock 注解告诉 Mockito 创建一个 SomeService 类型的模拟对象,并将其赋值给 someService 变量。
  • 通常与 @RunWith(MockitoJUnitRunner.class)MockitoAnnotations.initMocks(this) 一起使用。

2. @InjectMocks

用于创建类的实例并注入模拟对象。

@InjectMocks
private SomeController someController;
  • @InjectMocks 注解告诉 Mockito 创建 SomeController 类的实例,并将用 @Mock 注解创建的模拟对象注入到该实例中。
  • 依赖注入的方式可以是构造函数注入、方法注入或字段注入。

3. @Spy

用于部分模拟对象,即部分真实部分模拟。

@Spy
private SomeService someService = new SomeService();
  • @Spy 注解告诉 Mockito 创建一个部分模拟对象,该对象在调用真实方法时将实际调用,而在调用模拟方法时则使用模拟行为。
  • 也可以使用 Mockito.spy() 方法来创建部分模拟对象。

4. @Captor

用于创建并自动注入 ArgumentCaptor,用于捕获方法调用时的参数。

@Captor
private ArgumentCaptor<SomeArgument> argumentCaptor;
  • @Captor 注解告诉 Mockito 创建一个 ArgumentCaptor 对象,并将其注入到 argumentCaptor 变量中。
  • ArgumentCaptor 可以用来捕获方法调用时的参数,以便在测试中进行验证。

实操

概念知识就只有这么一点,但是实际的场景使用该怎么用呢?像我们常用的开发场景,有controller、service、mapper等等,该怎么开展测试呢,又有哪些需要注意的地方?

场景举例

假设我需要通过id获取到用户的信息。

假设我们有以下层次结构:

  1. Controller层:处理HTTP请求。
  2. Service层:包含业务逻辑。
  3. Repository层(或Mapper层):与数据库交互。

实现代码

首先,我们定义Controller、Service和Repository层。

1. 用户模型
public class User {
    private Long id;
    private String name;
    private String email;

    // Constructors, getters, setters, etc.
}
2. Repository层
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository {
    User findById(Long id);
}
3. Service层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUserById(Long id) {
        return userRepository.findById(id);
    }
}
4. Controller层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }
}

单元测试

使用Mockito来完成Service层和Controller层的单元测试。

1. Service层单元测试
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    public UserServiceTest() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testGetUserById() {
        User mockUser = new User(1L, "John Doe", "john.doe@example.com");
        
        when(userRepository.findById(1L)).thenReturn(mockUser);

        User user = userService.getUserById(1L);

        assertEquals(1L, user.getId());
        assertEquals("John Doe", user.getName());
        assertEquals("john.doe@example.com", user.getEmail());

        verify(userRepository).findById(1L);
    }
}
2. Controller层单元测试
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.http.MediaType;

@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @InjectMocks
    private UserController userController;

    @BeforeEach
    public void setUp() {
        MockitoAnnotations.openMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }

    @Test
    public void testGetUserById() throws Exception {
        User mockUser = new User(1L, "John Doe", "john.doe@example.com");
        
        when(userService.getUserById(1L)).thenReturn(mockUser);

        mockMvc.perform(get("/users/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1L))
                .andExpect(jsonPath("$.name").value("John Doe"))
                .andExpect(jsonPath("$.email").value("john.doe@example.com"));

        verify(userService).getUserById(1L);
    }
}

解释

  1. Service层测试:使用@Mock注解模拟UserRepository,并使用@InjectMocks注解注入到UserService中。通过when(...).thenReturn(...)设定模拟对象的行为,然后调用userService.getUserById方法并验证结果和行为。
  2. Controller层测试:使用@WebMvcTest注解来加载UserController,并使用@MockBean注解模拟UserService。通过mockMvc发送HTTP GET请求,并验证响应的状态和内容。

总结

单元测试的重点放在service层测试,因为service层是主要的业务逻辑整合。

@Mock注解只是用来注入到可能出现空指针的地方,所以@Mock注入的对象都不会得到真实的调用,比如当@Mock修饰UserRepository时,userRepository.findById()其实并没用真正查库去完成查询的,就算打断点也不会进入方法体。所以说白了@Mock就是用来修饰暂不关心的逻辑,但又要防止它空指针报错时用的。

@InjectMocks会真实创建一个该类的实例,调用方法也会进入到方法体,逐行执行。所以@InjectMocks是用来修饰主要要执行的逻辑,但是如果你的类继承了父类,并调用父类的方法,这会报空指针异常的。比如你使用了Mybatis-plus,你调用了userService.save(),会提示没有这个方法。这种时候就要使用@Spy来修饰UserService,随后调用就不会提示错误了,但是实际上并不会真的把数据存入到库里。

如果你在使用Mockito时需要真正从数据库中获得数据,你就得获得sqlSession对象,通过反射获得你的mapper类,再真实执行。

如果你需要测试接口的完整功能连通性,那还是得用@SpringbootTest或者通过接口调用去完成测试。

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mockito是一个用于Java的开源测试框架,用于创建和管理模拟对象(mock objects)。它可以帮助我们进行单元测试,特别是在测试依赖对象时非常有用。 在Mockito中,我们可以使用注解来简化模拟对象的创建。例如,使用`@Mock`注解可以创建一个模拟对象,使用`@InjectMocks`注解可以将模拟对象注入到被测试对象中。 Mockito还提供了一些方法来验证模拟对象的交互和行为。例如,使用`verify`方法可以验证方法是否被调用,使用`times`方法可以指定方法被调用的次数,使用`never`方法可以验证方法是否从未被调用。 另外,Mockito还支持设置模拟对象的行为。我们可以使用`when`方法来设置模拟对象方法的返回值,使用`doReturn`方法来设置模拟对象方法的行为。 总之,Mockito是一个强大的测试框架,可以帮助我们进行单元测试,并且使用注解可以简化模拟对象的创建。通过验证和设置模拟对象的行为,我们可以更好地测试我们的代码。 #### 引用[.reference_title] - *1* *2* *3* [【码农教程】手把手教你Mockito使用](https://blog.csdn.net/AI_Green/article/details/129163693)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值