欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Mockito编写Controller层单元测试

程序员文章站 2022-03-02 18:13:25
...

测试团队要求写单元测试,但是Mockito网上相关的文档不是很多,基本都是入门性质的没有更深层次的使用案例,而且Mockito本身功能也在不断的完善,导致写起来比较费劲,好多地方完全靠猜。摸索之下算是完成了,把踩过的坑记录一下,万一有人需要呢。


Controller层的单元测试比较简单,主要思路是mock业务层返回值,然后模拟调用接口,看两个例子就明白了。
Controller.java

@RestController
public class Controller {
	@Autowired
    private final IndiskService indiskService;
    private static final String AUTHORIZATION = "Authorization";
	
	@PostMapping("/ebs/{version}/volumes/{volume_id}/action/attach")
    public ReturnMsgVO attach(@PathVariable("volume_id") String volume_id,
                              @RequestBody JSONObject jsonObject,
                              HttpServletRequest request) {
        String keycloakToken = request.getHeader(AUTHORIZATION);
        AttachDetachExtendVO attachDetachExtendVO = new AttachDetachExtendVO();
		//do something......
		//正确返回200
        return indiskService.attachVolume(attachDetachExtendVO, request, keycloakToken);
    }

	@DeleteMapping("/ebs/{version}/volumes/{volume_id}/soft")
    public ReturnMsgVO softDeleteVolume(@PathVariable(value = "volume_id") String volumeId,
                                        @RequestBody String orderJson,
                                        HttpServletRequest request) {
		String keycloakToken = request.getHeader(AUTHORIZATION);
		//正确返回202,消息体为{"code":"202","message":null,"data":null}
        return indiskService.softDeleteVolume(volumeId, keycloakToken, orderJson);
    }
}

需要指出一点接口有token认证,认证字段存在于Header中,字段名为Authorization。
再来看一下对应的测试类,注意import时采用的是static引入

import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class ControllerTest {

    private static final String AUTHORIZATION = "Authorization";

    private MockMvc mvc;

    @Mock
    //要mock被测类中依赖的对象使用@Mock注解
    private ServiceImpl serviceImpl;

    @InjectMocks
    //被测类本身使用@InjectMocks注解
    private Controller controller;

    @Before
    public void setup() {
    	//初始化
        MockitoAnnotations.initMocks(this);
        //构建mvc环境
        mvc = MockMvcBuilders.standaloneSetup(controller).build();
    }


    @Test
    public void testSoftDeleteVolume() throws Exception {
        //mock方法行为
        Mockito.when(serviceImpl.softDeleteVolume("1", "2", "3"))
                .thenReturn(ReturnMsgVO.builder().code("202").build());

		//模拟接口调用
        this.mvc.perform(delete("/ebs/v1/volumes/1/soft")
                .header(AUTHORIZATION, "2").content("3"))
                .andExpect(status().isOk())
                //对接口响应进行验证
                .andExpect(content().json("{\"code\":\"202\",\"message\":null,\"data\":null}"));
    }

    @Test
    public void attach() throws Exception {
        Mockito.when(serviceImpl.attachVolume(Mockito.any(), Mockito.any(), Mockito.anyString()))
                .thenReturn(ReturnMsgUtil.success());

        mvc.perform(post("/ebs/v1/volumes/123/action/attach")
                .header(AUTHORIZATION, "123")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"os-attach\":{\"instance_uuid\":\"123\"}}"))
                .andExpect(status().isOk());
    }
}

总结一下要点:

  • Controller层单元测试不需要启动应用,也就不需要给类名加任何Test相关的注解
  • 注意import相关包的方式,有些人喜欢以static的方式引入,在网上参考他人代码时务必注意一下
  • mock方法行为时,需注意调用时的url请求参数应该与mock时的参数保持一致,否则mock不成功。(若使用AnyString()等方法mock可无视此条)
  • A类中依赖注入了B类,在写A的测试类时要注意:对A使用@InjectMocks注解,对于B使用@Mock注解
  • 不要忘记initMocks