在使用@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"); } }
这里依然使用 SpringRunner 执行测试。同时使用 @ SpringBootTest 注解的 RANDOM_PORT 模式来得到一个内嵌的WebServer运行当前应用。
测试代码中使用 RestTemplate 来触发请求,这个过程和使用外部服务器很像。
测试中的assertion现在有了一点儿变化,因为要验证的返回值由 MockHttpServletResponse 变成了 ResponseEntity 。
因为使用了 @ SpringBootTest 注解,就可以使用 @ Autowired 注解来获取 TestRestTemplate 的实例。这个 TestRestTemplate 实例的作用和常见的 RestTemplate 实例几乎没有任何区别,只是添加了一些额外的能力。实际上,可以将 TestRestTemplate 视为 RestTemplate 在测试环境的装饰类。
参与测试的角色关系如下图:
也许我们会觉得第一个方案会更加有性能优势,因为它不需要加载Spring Context。事实上确实如此。但是即使加载了SpringContext,也不会造成特别恐怖的影响,因为在同一个Test Suite中,已经加载的Spring Context是可以重用的。
不过Spring Context重用也会导致一些问题,比如一些测试方法对公共的Bean做了修改就可能会影响到其他测试方法。此时可以使用 @ DirtiesContext 注解来要求重新加载Context。
到现在为止我们从轻到重共介绍了四种SpringBoot Controller测试方案。
虽然我们的目标一直都是对Controller层进行测试,但是从第一种测试方案(Standalone MockMVC)到现在,测试的角度还是有些变化的。一开始我们只是会加载测试的Controller类,却不会加载其周边的一些角色如Filter或Advice。到现在这个方案里我们启动了内嵌的WebServer,加载了整个SpringBoot Context。
目前这个方案是提到的四个测试方案里最重的一个,也是离单元测试的概念最远的一个。
下面说几个使用建议:
其他:示例代码可在CSDN下载,地址:https://download.csdn.net/download/tianxiexingyun/11065824