最细粒度地实施单元测试,是对每个类单独进行单元测试。这个类的所有依赖使用模拟类来生成,这样我们就可以模拟各种情况,对类里面的所有分支逻辑进行完整地测试。

本篇文章,介绍使用SpringMVC框架下,对Controller层进行单元测试的场景。我们使用Mockitor来构造模拟类。

首先,列出原始程序代码示例。

Singer模型类如下

package cn.javaer.unittest.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Singer {
    private Integer id;
    private String name;
}

Controller类

package cn.javaer.unittest.controller;

import cn.javaer.unittest.model.Singer;
import cn.javaer.unittest.service.SingerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;

@Slf4j
@RestController
public class SingerController {

    @Autowired
    private SingerService singerService;

    @GetMapping("/v1/singer/find")
    public List<Singer> find(@RequestParam("name") String name) {
        log.info("findSingers name={}", name);
        if (name == null || name.isEmpty()) {
            log.warn("name is blank!");
            return Collections.emptyList();
        }
        return singerService.find(name);
    }
}

以上是一个非常简单的SpringMVC风格的web程序示例,由于SingerService具体的实现不影响我们展示如何实施单元测试,所以就不列出来了。

下面是SingerControllerTest进行单元测试的示例,用了junit 4.12

package cn.javaer.unittest.controller;

import cn.javaer.unittest.model.Singer;
import cn.javaer.unittest.service.SingerService;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@Slf4j
public class SingerControllerTest {

    @Test
    public void findSingers() {
        Singer singer3 = new Singer(3, "张三");
        Singer singer4 = new Singer(4, "张四");

        SingerService singerService = mock(SingerService.class);
        when(singerService.find("张")).thenReturn(ImmutableList.of(singer3, singer4));
        when(singerService.find(null)).thenThrow(new NullPointerException("name can not be null"));

        SingerController singerController = new SingerController();
        ReflectionTestUtils.setField(singerController, "singerService", singerService);

        List<Singer> singers = singerController.find("张");
        assertEquals(2, singers.size());

        List<Singer> emptySingers = singerController.find(null);
        assertEquals(0, emptySingers.size());
    }
}
  • 我们用了Mockitor的静态方法mock来构造SingerService的模拟类,when方法则定义了匹配某些调用时的条件,thenReturn和thenThrow则定义了基于某些条件要响应什么样的结果。
  • ReflectionTestUtils.setField帮助我们把singerService模拟类注入SingerController中
  • 最后用junit的Assert核对测试结果

如果正常运行无误,我们将会得到如下日志输出,说明我们的单元测试通过了

12:19:40.985 [main] DEBUG org.springframework.test.util.ReflectionTestUtils - Setting field 'singerService' of type [null] on target object [cn.javaer.unittest.controller.SingerController@3578436e] or target class [class cn.javaer.unittest.controller.SingerController] to value [Mock for SingerService, hashCode: 1885996206]
12:19:41.001 [main] INFO cn.javaer.unittest.controller.SingerController - findSingers name=张
12:19:41.013 [main] INFO cn.javaer.unittest.controller.SingerController - findSingers name=null
12:19:41.014 [main] WARN cn.javaer.unittest.controller.SingerController - name is blank!