转载

Spring Controller层测试 – 05 SpringBootTest & WebServer

在使用@SpringBootTest测试时可以指定一个端口,如 @ SpringBootTest ( webEnvironment = WebEnvironment . RANDOM_PORT ) @ SpringBootTest ( webEnvironment = WebEnvironment . DEFINED_PORT ) ,这样在测试时会启动Spring内嵌的Http Server。 这时就可以使用一个 RestTemplate 或者 TestRestTemplate

使用RANDOM_PORT和DEFINED_PORT的区别在于前着使用的是配置文件中的端口号( server . port ,默认值为8080),后者使用的是一个随机端口号。在进行并行测试的时候可以使用随机端口号以避免端口冲突。

看下测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WorkerControllerSpringBootTest {
 
 
    @Autowired
    private TestRestTemplate restTemplate;
 
 
    @Before
    public void setup() {
        System.out.println();
    }
 
    @Test
    public void getWhenExists() {
        //when
        ResponseEntity<Worker> response = restTemplate.getForEntity("/worker/2", Worker.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo(new Worker("raccoon", 23));
    }
 
 
    @Test
    public void getWhenNotExists() {
        //when
        ResponseEntity<Worker> response = restTemplate.getForEntity("/worker/26", Worker.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        assertThat(response.getBody()).isNull();
    }
 
 
    @Test
    public void getByNameWhenExists() {
        //when
        ResponseEntity<Worker> response = restTemplate.getForEntity("/worker?name=HanMeimei", Worker.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo(new Worker("HanMeimei", 16));
    }
 
 
    @Test
    public void getByNameWhenNotExists() {
        //when
        ResponseEntity<Worker> response = restTemplate.getForEntity("/worker?name=LiLei", Worker.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isNull();
    }
 
 
    @Test
    public void add() {
        ResponseEntity<Worker> response =
                restTemplate.postForEntity("/worker", new Worker("Jerry", 12), Worker.class);
 
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
    }
 
 
    @Test
    public void workerFilter() {
        //when
        ResponseEntity<Worker> response = restTemplate.getForEntity("/worker/2", Worker.class);
        //then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getHeaders().get("X-CHOBIT-APP")).containsOnly("chobit-header");
    }
 
}

Web Server测试

这里依然使用 SpringRunner 执行测试。同时使用 @ SpringBootTest 注解的 RANDOM_PORT 模式来得到一个内嵌的WebServer运行当前应用。

测试代码中使用 RestTemplate 来触发请求,这个过程和使用外部服务器很像。

测试中的assertion现在有了一点儿变化,因为要验证的返回值由 MockHttpServletResponse 变成了 ResponseEntity

TestRestTemplate

因为使用了 @ SpringBootTest 注解,就可以使用 @ Autowired 注解来获取 TestRestTemplate 的实例。这个 TestRestTemplate 实例的作用和常见的 RestTemplate 实例几乎没有任何区别,只是添加了一些额外的能力。实际上,可以将 TestRestTemplate 视为 RestTemplate 在测试环境的装饰类。

参与测试的角色关系如下图:

Spring Controller层测试 – 05 SpringBootTest &amp; WebServer

关于性能

也许我们会觉得第一个方案会更加有性能优势,因为它不需要加载Spring Context。事实上确实如此。但是即使加载了SpringContext,也不会造成特别恐怖的影响,因为在同一个Test Suite中,已经加载的Spring Context是可以重用的。

不过Spring Context重用也会导致一些问题,比如一些测试方法对公共的Bean做了修改就可能会影响到其他测试方法。此时可以使用 @ DirtiesContext 注解来要求重新加载Context。

总结

到现在为止我们从轻到重共介绍了四种SpringBoot Controller测试方案。

虽然我们的目标一直都是对Controller层进行测试,但是从第一种测试方案(Standalone MockMVC)到现在,测试的角度还是有些变化的。一开始我们只是会加载测试的Controller类,却不会加载其周边的一些角色如Filter或Advice。到现在这个方案里我们启动了内嵌的WebServer,加载了整个SpringBoot Context。

目前这个方案是提到的四个测试方案里最重的一个,也是离单元测试的概念最远的一个。

下面说几个使用建议:

  • 如果在单元测试中关注Controller的逻辑,优先选择第一种方案:Standalone MockMVC方案;
  • 如果要测试Web层的其它角色(Filter或Advice)的行为,优先选择第四种测试方案执行集成测试;
  • 避免将单元测试和集成测试混在一起,最好分开来写。

其他:示例代码可在CSDN下载,地址:https://download.csdn.net/download/tianxiexingyun/11065824

原文  http://www.zhyea.com/2019/03/29/spring-controller-test-05-springboottest-webserver.html
正文到此结束
Loading...