網頁

2020/4/10

Spring Boot Test @SpringBootTest的作用

在Spring Boot專案撰寫單元測試(Unit Test)時要在測試類別前加上@SpringBootTest注釋,例如下面是被測試的Controller類別。

DemoController

package com.abc.demo.controller;

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

import javax.servlet.http.HttpServletRequest;

@RestController
public class DemoController {

    @GetMapping(value = "/hello")
    public String hello(HttpServletRequest request) {
        String hello = "hello";
        System.out.println(hello); // hello
        return hello;
    }
} 

下面則是用來測試DemoController的測試類,類別名稱前會加上@SpringBootTest

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoControllerTests {

    @Autowired
    private DemoController demoController;

    @Test
    void hello_ReturnHello() throws Exception {

        String actual = demoController.hello();
        String expected = "hello";
        Assertions.assertEquals(expected, actual);

    }
}

@SpringBootTest的用途為找到@SpringBootApplication主配置類別來啟動Spring Boot應用程式環境。

@SpringBootTest預設不會啟動server來執行測試,不過可透過webEnvironment屬性設定測試時的web環境,設定值如下:

  • MOCK(預設):載入使用mock的WebApplicationContext環境(mock web環境),不會啟動內置server。若classpath沒有Spring Web設定,則載入無web環境的ApplicationContext
    測試web程式時通常會搭配@AutoConfigureMockMvc@AutoConfigureWebTestClient做mock測試。
  • RANDOM_PORT:載入真實的WebServerApplicationContext,會啟動監聽隨機port的內置server。
  • DEFINED_PORT:載入真實的WebServerApplicationContext,會啟動監聽指定port的內置server。port定義在application.properties,預設8080
  • NONE:載入無web環境的ApplicationContext

各設定測試DemoController範例如下。

範例環境:

  • Java 1.8
  • Spring Boot 2.2.1.RELEASE
  • JUnit 5

範例的application.properties設定。

application.properties

#context path
server.servlet.context-path=/demo
#port
server.port=8080


SpringBootTest.WebEnvironment.MOCK

使用MOCK,則執行測試時為mock Web環境,server不啟動,因此搭配@AutoConfigureMockMvc並注入MockMvc來做mock端點(endpoints)測試。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class DemoControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void hello_ReturnHello() throws Exception {

        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("hello")); // pass

    }
}


SpringBootTest.WebEnvironment.RANDOM_PORT

使用RANDOM_PORT,則執行測試時為真實的web環境,server會啟動,port號為隨機。可使用@LocalServerPort注入使用的隨機port號。利用TestRestTemplate對端點發送請求來進行測試。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoControllerTests {

    @Autowired
    private TestRestTemplate testRestTemplate;

    @LocalServerPort
    private int port; // random port

    @Test
    void hello_ReturnHello() throws Exception {

        String url = "http://localhost:" + port + "/demo/hello"; // http://localhost:62214/demo/hello 
        System.out.println(url);

        Assertions.assertTrue(
                testRestTemplate.getForObject(url, String.class)
                        .contains("hello")); // pass

    }
}


SpringBootTest.WebEnvironment.DEFINED_PORT

使用DEFINED_PORT,則執行測試時為真實的web環境,server會啟動,port號為application.properties中設定的port號。可使用@LocalServerPort注入port號。利用TestRestTemplate對端點發送請求來進行測試。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class DemoControllerTests {

    @Autowired
    private TestRestTemplate testRestTemplate;

    @LocalServerPort
    private int port;

    @Test
    void hello_ReturnHello() throws Exception {

        String url = "http://localhost:" + port + "/demo/hello"; // http://localhost:8080/demo/hello
        System.out.println(url);

        Assertions.assertTrue(
                testRestTemplate.getForObject(url, String.class)
                        .contains("hello")); // pass

    }
}


SpringBootTest.WebEnvironment.NONE

使用NONE,則執行測試時無web環境,不啟動server,所以無法做真正或mock的端點測試。

DemoControllerTests

package com.abc.demo.controller;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
class DemoControllerTests {

    @Autowired
    private DemoController demoController;

    @Test
    void hello_ReturnHello() throws Exception {

        String actual = demoController.hello();
        String expected = "hello";
        Assertions.assertEquals(expected, actual); // pass

    }
}

參考:

2 則留言:

  1. 最近在試spring boot 的整合測試,啟動test時遇到一堆警告訊息如下:
    Did not detect default resource location for test class
    以及想實際讓spring在單元測試時啟動可以模擬用client連線進行整合測試,網路找了一堆都是片段治標不治本解法,而且我試了也不行,搜尋到最後看到你這篇時我心想穩了(因為認識你,哈哈),應該是有用的,之後詳細看了一下文章裡的範例很完整,最後測試也可以用,非常感謝你

    回覆刪除
  2. 哈哈,謝謝JAVA吉他手的抬舉,能幫到您我很開心。

    回覆刪除