美文网首页
使用PowerMockito做测试

使用PowerMockito做测试

作者: 阿呆少爷 | 来源:发表于2018-12-12 22:16 被阅读59次

    典型的Spring应用会分为三层,分别是DAO、Service、Controller三层。Controller主要负责请求接入,具体的逻辑交给Service完成。要测试Controller,往往需要Mock Service,通过MockMVC+PowerMockito这个组合很适合做这个测试。

    引入依赖

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>2.23.0</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.0-RC.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.0-RC.3</version>
        <scope>test</scope>
    </dependency>
    

    测试准备

    使用@InjectMocks注入要测试的Controller。如果Controller依赖Service,那么通过@Mock注入这些Service。

    @InjectMocks
    private TaskController taskController;
    
    @Mock
    private TaskService taskService;
    

    Controller往往会注入全局的异常处理。在测试环境下,需要手动设置这个异常处理类。

    final StaticApplicationContext applicationContext = new StaticApplicationContext();
    applicationContext.registerSingleton("exceptionHandler", GlobalControllerExceptionHandler.class);
    
    final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
    webMvcConfigurationSupport.setApplicationContext(applicationContext);
    

    基本使用

    首先设置好mock方法的返回结果。

    PowerMockito.when(taskService.createTask(task)).thenReturn(task);
    

    接着调用这个mock方法。

    RequestBuilder request = post("/task")
            .contentType(APPLICATION_JSON_UTF8)
            .content(requestJson);
    
    MvcResult result = mvc.perform(request).andReturn();
    

    一些问题

    使用Java8时间

    如果对象使用Java 8的时间类型,测试过程会遇到很多问题。比如Task对象有两个属性是OffsetDateTime类型。

    public class Task {
    
        OffsetDateTime datetimeStartTask;
    
        OffsetDateTime datetimeEndTask;
    }
    

    目前我的解决办法是,序列化使用Gson,对Java8的时间类型支持很好。这样生成的requestJson可以被反序列化转换成对象。

    String requestJson = gson.toJson(task, Task.class);
    
    RequestBuilder request = post("/task")
            .contentType(APPLICATION_JSON_UTF8)
            .content(requestJson);
    
    MvcResult result = mvc.perform(request).andReturn();
    
    assert (result.getResponse().getStatus() == HttpStatus.CREATED.value());
    

    反序列的时候,要提前设置WRITE_DATES_AS_TIMESTAMPS=false,要不然SpringMVC使用Jackson将OffsetDateTime序列化成Timestamp,反序列的时候会报错。设置的正确方法如下所示。MockMvcBuilders要同时设置全局异常和消息转换类。

    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new
            MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setObjectMapper(builder.build());
    
    mvc = MockMvcBuilders.standaloneSetup(taskController)
            .setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver())
            .setMessageConverters(mappingJackson2HttpMessageConverter)
            .build();
    

    Objects.equals居然会认为2018-12-12T12:19:13.603Z2018-12-12T20:19:13.603+0800这两个时间不相等,使用toEpochSecond方法转成Timestamp比较就好了。

    jshell> OffsetDateTime t1 = OffsetDateTime.parse("2018-12-12T12:19:13.603Z")
    t1 ==> 2018-12-12T12:19:13.603Z
    
    jshell> OffsetDateTime t2= OffsetDateTime.parse("2018-12-12T20:19:13.603+08:00")
    t2 ==> 2018-12-12T20:19:13.603+08:00
    
    jshell> t1.toEpochSecond()
    $18 ==> 1544617153
    
    jshell> t2.toEpochSecond()
    $19 ==> 1544617153
    
    jshell> Objects.equals(t1, t2)
    $17 ==> false
    
    jshell> ZonedDateTime t1 = ZonedDateTime.parse("2018-12-12T12:19:13.603Z")
    t1 ==> 2018-12-12T12:19:13.603Z
    
    jshell> ZonedDateTime t2 = ZonedDateTime.parse("2018-12-12T20:19:13.603+08:00")
    t2 ==> 2018-12-12T20:19:13.603+08:00
    
    jshell> Objects.equals(t1, t2)
    $22 ==> false
    

    使用对象参数

    当when里面的方法使用对象作为参数时,传入的对象与反序列化得到的对象并不相等,这会导致无法触发when的条件,所以需要实现对象的hashcode和equalTo方法。

    PowerMockito.when(taskService.createTask(task)).thenReturn(task);
    
    objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
    ObjectWriter ow = objectMapper.writer().withDefaultPrettyPrinter();
    String requestJson = ow.writeValueAsString(task);
    
    RequestBuilder request = post("/task")
            .contentType(APPLICATION_JSON_UTF8)
            .content(requestJson);
    
    MvcResult result = mvc.perform(request).andReturn();
    

    Path参数校验

    模型的校验可以测试到。比如传入空的name,会触发MethodArgumentNotValidException异常。

    public class Task {
    
       @Size(min = 1, max = 128)
       String name;
    }
    

    PathVariable和RequestParam的校验测试不到,很奇怪。

    //id传入-2也行
    @GetMapping(value="/task/{id}")
    public ResponseEntity<Task> getTask(@PathVariable @Range(min=1) Long id) throws ApiException {
    
        Task task = taskService.getTaskById(id);
        return new ResponseEntity<>(task, HttpStatus.OK);
    }
    
    //pageNum传入1000也行
    @GetMapping("/tasks")
    public ResponseEntity<List<Task>> listTasks(@RequestParam(defaultValue = "1") @Range(min=1, max=100) Integer pageNum,
                                                @RequestParam(defaultValue = "20") Integer pageSize) {
    
        PageHelper.startPage(pageNum, pageSize);
        List<Task> taskList = taskService.listTasks(pageNum, pageSize);
        return new ResponseEntity<>(taskList, HttpStatus.OK);
    }
    
    image.png

    参考文章

    1. Mockito与PowerMock的使用基础教程

    相关文章

      网友评论

          本文标题:使用PowerMockito做测试

          本文链接:https://www.haomeiwen.com/subject/vjmlhqtx.html