测试框架01-MockMvc、Mock和Spy

 


测试框架01-MockMvc、Mock和Spy

1.MockMvc

@SpringBootTest
@AutoConfigureMockMvc // 将MockMvc注入到容器中
class UserControllerTest {

    @Resource
    private MockMvc mockMvc;

    @Test
    @DisplayName("通过id获取用户") // 为测试用例添加名称
    void getUserByIdTest() throws Exception {
        // 构建GET请求
        RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/get/1");

        mockMvc.perform(requestBuilder)
                // 响应状态为200。
                .andExpect(MockMvcResultMatchers.status().isOk())
                // 响应的数据。
                .andExpect(MockMvcResultMatchers.content().string(Matchers.equalTo("{\"id\":1,\"age\":null,\"name\":\"A01\",\"phone\":null,\"address\":null,\"info\":null}")))
                // 输出详细的请求和响应的内容。
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    @DisplayName("通过用户名模糊搜索")
    void getUserByNameSearch() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/getUserByNameSearch?name=1");

        MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();

        // 获取响应
        MockHttpServletResponse mockHttpServletResponse = mvcResult.getResponse();
        System.out.println(mockHttpServletResponse.getStatus()); // 200
        // {"id":1,"age":null,"name":"A01","phone":null,"address":null,"info":null}
        System.out.println(mockHttpServletResponse.getContentAsString());
    }

    @Test
    @DisplayName("保存用户")
    void save() throws Exception {
        // 构建POST请求。
        RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/save")
                // 请求数据。
                .content("{\"id\":4,\"age\":null,\"name\":\"A04\",\"phone\":null,\"address\":null,\"info\":null}")
                // 设置请求格式。
                .contentType(MediaType.APPLICATION_JSON_VALUE);

        mockMvc.perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
}

2.Mock

  1. Mock的简单使用。
class UserMapperTest01 {

    @Test
    @DisplayName("Mock的简单使用")
    void getUserByIdTest() {
        UserEntity userEntity = new UserEntity();
        userEntity.setId(1);

        // Mock出UserMapper对象
        UserMapper userMapper = Mockito.mock(UserMapper.class);
        // Mock出对象的类型,UserMapper$MockitoMock$aAjtLRsk
        System.out.println(userMapper.getClass().getName());

        // 打桩,调用userMapper.getUserById(1);时,永远返回userEntity。
        Mockito.when(userMapper.getUserById(1)).thenReturn(userEntity);

        Assertions.assertEquals(1, userMapper.getUserById(1).getId());
        // 对于没有打桩的情况,返回默认值null,int类型的数据返回0。
        Assertions.assertNull(userMapper.getUserById(0));
    }
}
  1. Mock对象的创建。
class UserMapperTest02 {

    /**
     * 使用Mock注解创建对象,需要初始化Mock环境,即让Mock的对象UserMapper有值。
     * 初始化Mock环境的两种方式:
     *      1) 在测试前,执行MockitoAnnotations.openMocks(this);
     *      2) 测试类上加,@RunWith(MockitoJUnitRunner.class)。
     */
    @Mock
    private UserMapper userMapper;

    @BeforeEach
    void beforeEach() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @DisplayName("通过Mock注解创建对象,Mock对象方法参数匹配。")
    void getUserByIdTest() {
        // Mockito.anyInt(),任何参数都返回null。
        Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenReturn(null);

        Assertions.assertNull(userMapper.getUserById(0));
        Assertions.assertNull(userMapper.getUserById(1));
    }

    @Test
    @DisplayName("Mock异常")
    void getUserByIdThrowsException() {
        Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenThrow(new RuntimeException("运行时错误"));

        Assertions.assertThrowsExactly(RuntimeException.class, () -> userMapper.getUserById(1));

        // 对于没有返回值的方法,不能使用thenThrow()来抛出异常,可以使用doThrow()来抛出异常。
        Mockito.doThrow(new RuntimeException("运行时错误")).when(userMapper).getUserById(1);
        Assertions.assertThrowsExactly(RuntimeException.class, () -> userMapper.getUserById(1));
    }

    @Test
    @DisplayName("Mock出多个返回值")
    void getUserByIdMoreReturnValue() {
        UserEntity userEntity01 = new UserEntity();
        userEntity01.setId(1);

        UserEntity userEntity02 = new UserEntity();
        userEntity02.setId(2);

        UserEntity userEntity03 = new UserEntity();
        userEntity03.setId(3);

        Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenReturn(userEntity01, userEntity02, userEntity03);

        // thenReturn()有多个参数,调用时会依次返回每个参数,超过参数数量的调用会返回最后一个参数。
        Assertions.assertEquals(userEntity01, userMapper.getUserById(1));
        Assertions.assertEquals(userEntity02, userMapper.getUserById(2));
        Assertions.assertEquals(userEntity03, userMapper.getUserById(3));
        Assertions.assertEquals(userEntity03, userMapper.getUserById(3));
    }
}

3.Spy

/**
 * Spy和Mock的区别,Spy会走真实的方法。
 */
class UserServiceTest01 {

    @Test
    @DisplayName("Spy的简单使用")
    void getUserById() {
        UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);

        // Spy会走真实的方法,userService.getUserById(1)中依赖UserMapper,所有抛出空指针异常。
        Assertions.assertThrowsExactly(NullPointerException.class, () -> userService.getUserById(1));
    }

    @Test
    @DisplayName("Spy打桩")
    void getUserByIdBySpyMock() {
        UserEntity userEntity01 = new UserEntity();
        userEntity01.setId(1);

        UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);

        // Spy出的对象打桩之后,就不会走真实的方法了,而是会返回打桩的默认值。
        Mockito.doReturn(userEntity01).when(userService).getUserById(Mockito.anyInt());

        Assertions.assertEquals(userEntity01, userService.getUserById(1));
    }
}

4.InjectMocks

class UserServiceTest02 {

    @Mock
    private UserMapper userMapper;
    @InjectMocks // @InjectMocks会将@Mock和@Spy出的对象注入到当前对象中。
    private UserServiceImpl userService;

    @BeforeEach
    void beforeEach() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @DisplayName("测试获取用户id")
    void getUserById() {
        // 通过@InjectMocks注解将userService内部依赖的userMapper注入,
        // 所以userService.getUserById(1);在调用时没有抛出空指针异常。
        UserEntity userEntity = userService.getUserById(1);

        // 由于userMapper是Mock出来的,所以方法的返回值为默认值null。
        Assertions.assertNull(userEntity);

        UserEntity userEntity01 = new UserEntity();
        userEntity01.setId(1);
        // 给userMapper.getUserById()方法打桩,所以相当于改变了userService.getUserById(1);的返回值。
        Mockito.doReturn(userEntity01).when(userMapper).getUserById(Mockito.anyInt());

        Assertions.assertEquals(userEntity01, userService.getUserById(1));
    }
}

5.Mockito的其他方法

class UserServiceTest02 {

    @Mock
    private UserMapper userMapper;
    @InjectMocks // @InjectMocks会将@Mock和@Spy出的对象注入到当前对象中。
    private UserServiceImpl userService;

    @BeforeEach
    void beforeEach() {
        MockitoAnnotations.openMocks(this);
    }

    /**
     * thenAnswer()和then(),功能相同,都可以根据不同的参数返回不同的值。
     */
    @Test
    @DisplayName("thenAnswer()和then()")
    void test01() {
        UserEntity userEntity01 = new UserEntity();
        userEntity01.setId(1);

        Mockito.when(userMapper.getUserById(Mockito.anyInt())).thenAnswer(new Answer<UserEntity>() {
            @Override
            public UserEntity answer(InvocationOnMock invocation) throws Throwable {

                // 获取方法执行的参数
                Object[] arguments = invocation.getArguments();

                int arg = (int) arguments[0];

                if (arg == 1) {
                    return userEntity01;
                }
                return null;
            }
        });

        Assertions.assertEquals(userEntity01, userMapper.getUserById(1));
        Assertions.assertNull(userMapper.getUserById(10));
    }

    /**
     * doNothing() 让void方法什么都不做。
     * Mock出的对象,本来就说明都不做,所有更适合于Spy创建出的对象。
     */
    @Test
    @DisplayName("doNothing()")
    void test02() {
        UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);

        Mockito.doNothing().when(userService).update(null);
    }

    /**
     * reset() 重置Mock对象,让其返回默认值。
     */
    @Test
    @DisplayName("reset()")
    void test03() {
        UserEntity userEntity01 = new UserEntity();
        userEntity01.setId(1);

        Mockito.doReturn(userEntity01).when(userMapper).getUserById(Mockito.anyInt());

        Assertions.assertEquals(userEntity01, userMapper.getUserById(1));

        // reset()后,Mock出的对象会返回默认值。
        Mockito.reset(userMapper);

        Assertions.assertNull(userMapper.getUserById(1));
    }

    /**
     * thenCallRealMethod() 让Spy的某个方法调用真实的对象。
     */
    @Test
    @DisplayName("thenCallRealMethod()")
    void test04() {

        UserServiceImpl userService = Mockito.spy(UserServiceImpl.class);

        // Spy创建UserServiceImpl在getUserById()是会抛出空指针异常,现在让其返回null。
        Mockito.doReturn(null).when(userService).getUserById(Mockito.anyInt());
        Assertions.assertNull(userService.getUserById(1));

        // 使用doCallRealMethod()后调用真实的方法,所以会抛出空指针异常。
        Mockito.doCallRealMethod().when(userService).getUserById(Mockito.anyInt());
        Assertions.assertThrowsExactly(NullPointerException.class, () -> userService.getUserById(1));
    }

    /**
     * verify() 验证是否调用某个方法。
     */
    @Test
    void test05() {
        // 验证这个方法从没有被调用过
        Mockito.verify(userMapper, Mockito.never()).getUserById(1);

        userMapper.getUserById(1);
        // 验证这个方法至少调用了1次
        Mockito.verify(userMapper, Mockito.atLeastOnce()).getUserById(1);

        userMapper.getUserById(1);
        // 验证当前方法调用了2次
        Mockito.verify(userMapper, Mockito.times(2)).getUserById(1);
    }

    /**
     * 验证对象是否是Spy或者Mock对象。
     */
    @Test
    void test06() {
        UserServiceImpl spy = Mockito.spy(UserServiceImpl.class);
        UserServiceImpl mock = Mockito.mock(UserServiceImpl.class);

        Assertions.assertTrue(Mockito.mockingDetails(spy).isSpy());
        Assertions.assertTrue(Mockito.mockingDetails(mock).isMock());
    }
}

6.MockBean和SpyBean

@SpringBootTest
class UserServiceTest03 {

    // @MockBean,会替换Spring容器中出现的相同类型的类,并且调用方法会走真实的方法。
    @SpyBean
    private UserMapper userMapper;

    // @MockBean,会替换Spring容器中出现的相同类型的类,并且调用方法不会走真实的方法。
    @MockBean
    private IUserService userService;
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void getUserByIdMockBean() {
        // com.rainbow.test01.service.IUserService$MockitoMock$1x9g3fQ2
        System.out.println(userService.getClass().getName());

        IUserService bean = applicationContext.getBean(IUserService.class);
        // com.rainbow.test01.service.IUserService$MockitoMock$6JqkL7SC
        System.out.println(bean.getClass().getName());

        // @MockBean创建的对象,和@Mock创建的对象相同,也不会调用真实的方法,每个方法都会返回默认值。
        Assertions.assertNull(userService.getUserById(1));

        UserEntity userEntity = new UserEntity();
        // 打桩,@MockBean和@Mock的用法一样。
        Mockito.doReturn(userEntity).when(userService).getUserById(1);
        Assertions.assertEquals(userEntity, userService.getUserById(1));
    }

    @Test
    void getUserByIdSpyBean() {
        // com.rainbow.test01.mapper.UserMapper$MockitoMock$5OalTLnn$$EnhancerBySpringCGLIB$$4acf6361
        System.out.println(userMapper.getClass().getName());

        Assertions.assertEquals(1, userMapper.getUserById(1).getId());
    }
}

7.自动化测试

  1. 测试可以简单的分为单元测试、集成测试、性能测试等。
  2. 单元测试顾名思义对一个小的单元进行测试,不需要考虑Spring环境、数据库等,常常可以使用Mock、Spy注解来完成。
  3. 集成测试。集成测试就需要依赖于Spring上下文、数据库等,常常可以使用MockBean、SpyBean来完成。
posted @   行稳致远方  阅读(267)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示