Spring Boot 基于 JUnit 4 实现单元测试

本文介绍 Spring Boot 2 基于 JUnit 4 的单元测试实现方案。


目录

  • 开发环境
  • 测试 HTTP 接口
  • Mock 调用对象
  • 测试套件
  • 总结

开发环境

  • JDK 8

测试 HTTP 接口

Spring Boot 单元测试覆盖的一个最常见的业务场景是 HTTP 接口测试,以下演示如何基于 Spring Boot 单元测试框架测试常见的 HTTP 接口。

  1. 创建 Spring Boot 工程,参考:IntelliJ IDEA 创建 Spring Boot 工程。

  2. 修改工程 pom 文件,添加 spring-boot-starter-webjunit 依赖。



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.3.RELEASE
    
    tutorial.spring.boot
    spring-boot-unit-test
    0.0.1-SNAPSHOT
    spring-boot-unit-test
    Spring Boot 单元测试示例

    
        1.8
        UTF-8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            junit
            junit
            4.12
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            repackage
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.0
                
                    ${java.version}
                    ${java.version}
                    ${project.build.encoding.source}
                
            
        
    


  1. Spring Boot 工程 src\test\java\tutorial\spring\boot 目录下自动生成了一个单元测试类。
package tutorial.spring.boot;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UnitTestApplicationTests {

    @Test
    public void contextLoads() {
    }

}
  1. 添加待测试的 Controller 方法,HTTP 接口通常定义在 Controller 层。
package tutorial.spring.boot.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    @GetMapping(value = "/")
    public String index() {
        return "Just for Spring Boot unit test!";
    }
}
  1. 添加单元测试类。
package tutorial.spring.boot.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

@RunWith(SpringRunner.class)
@WebMvcTest(IndexController.class)
public class IndexControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testIndex() throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.get("/"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string("Just for Spring Boot unit test!"));
    }
}

单元测试运行结果(略)。
上述示例演示如何测试不带参数的 GET 方法,实际应用中 HTTP 接口都要求请求参数,以下演示如何测试带参数的 POST 方法。

  1. 修改 IndexController,添加一个新的 POST 接口。
@PostMapping(value = "/commit")
public String commit(String content) {
    System.out.printf("Commit content: %s%n", content);
    return "Received content: " + content;
}
  1. 修改 IndexControllerTest,添加测试新增 POST 接口的方法。
@Test
public void testCommit() throws Exception {
    this.mvc.perform(MockMvcRequestBuilders.post("/commit"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().string("Received content: null"));
    String content = "[Commit Content]";
    this.mvc.perform(MockMvcRequestBuilders.post("/commit").param("content", content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().string("Received content: " + content));
}

单元测试运行结果(略)。
GET / POST 接口外的其它接口 PUT / DELETE / PATCH 测试方法类似,只需调用 org.springframework.test.web.servlet.request.MockMvcRequestBuilders 的不同方法构建不同类型的 HTTP 请求。


Mock 调用对象

通常,Controller 层职责只包含参数校验、包装返回值或重定向等,具体的业务处理会调用 Service 层对象完成,以下演示如何 Mock Service 层对象实现 Controller 层的独立测试。

  1. 添加 Service 接口。
package tutorial.spring.boot.service;

public interface IndexService {

    String commit(String content);
}
  1. 添加 Service 接口实现。
package tutorial.spring.boot.service.impl;

import org.springframework.stereotype.Service;
import tutorial.spring.boot.service.IndexService;

@Service
public class IndexServiceImpl implements IndexService {

    @Override
    public String commit(String content) {
        System.out.printf("Param[content] is: %s%n", content);
        return "Dealed " + content;
    }
}
  1. 改造 Controller 中方法,调用 Service 方法返回。
package tutorial.spring.boot.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import tutorial.spring.boot.service.IndexService;

@RestController
public class IndexController {

    private final IndexService indexService;

    public IndexController(IndexService indexService) {
        this.indexService = indexService;
    }

    @GetMapping(value = "/")
    public String index() {
        return "Just for Spring Boot unit test!";
    }

    @PostMapping(value = "/commit")
    public String commit(String content) {
        System.out.printf("Commit content: %s%n", content);
        return indexService.commit(content);
    }
}
  1. 修改单元测试方法。
package tutorial.spring.boot.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import tutorial.spring.boot.service.IndexService;

@RunWith(SpringRunner.class)
@WebMvcTest(IndexController.class)
public class IndexControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private IndexService indexService;

    @Test
    public void testIndex() throws Exception {
        this.mvc.perform(MockMvcRequestBuilders.get("/"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string("Just for Spring Boot unit test!"));
    }

    @Test
    public void testCommit() throws Exception {
        String request = "test";
        String response = "mock response";
        Mockito.when(indexService.commit(request)).thenReturn(response);
        this.mvc.perform(MockMvcRequestBuilders.post("/commit").param("content", request))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andExpect(MockMvcResultMatchers.content().string(response));
    }
}

单元测试运行结果(略)。

  1. 编写 Service 单元测试类
package tutorial.spring.boot.service;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class IndexServiceTest {

    @Autowired
    private IndexService indexService;

    @Test
    public void testNotNull() {
        Assert.assertNotNull(indexService);
    }

    @Test
    public void testCommit() {
        String content = "test";
        Assert.assertEquals("Dealed " + content, indexService.commit(content));
    }
}

单元测试运行结果(略)。


测试套件

如果有很多单元测试类,可以放在同一个测试套件中运行。

package tutorial.spring.boot;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import tutorial.spring.boot.controller.IndexControllerTest;
import tutorial.spring.boot.service.IndexServiceTest;

@RunWith(Suite.class)
@Suite.SuiteClasses({IndexControllerTest.class, IndexServiceTest.class})
public class SuiteTests {
}

单元测试运行结果(略)。


总结

有关 Spring Boot 和 Spring MVC 更详细的单元测试,请参考官方文档

  • Spring Boot Reference Guide
  • Web on Servlet Stack(Testing)

你可能感兴趣的:(Spring Boot 基于 JUnit 4 实现单元测试)