在 Spring Boot 1.5.3 中進行 Spring MVC 測試

在使用 Spring boot 的過程當中發現 Spring Boot 的版本更迭很是的快,而不一樣的版本的不少語法和支持都有必定的區別,當遇到一個問題去 stackoverflow 搜索的時候常常會發現不一樣版本的解決方案,弄得我非常苦惱。(真是找到了用 npm 的感受,每次升級包都會出問題。每到這個時候就念到了 rails 的好,一個成熟的、穩定、合理的生態體系是多麼的重要!)。因此在這裏我明確的在標題裏提到了我所使用的版本 1.5.3java

Spring 官網提供了太多的 Getting Started 好比這個或者是 Hello World 的示例。這些示例真的是太太太簡單了,徹底沒辦法做爲學習的材料(再次強調,能不能看看人家 Rails 官方的 Guide 呀),而去其餘地方搜索的內容又多是早期版本(由於版本更迭快呀)的內容,把不一樣的版本中的衆多實踐方式進行比較並拼湊在一塊兒也非常浪費時間,因此我在這裏就在官方的基礎上座一個稍微多一點的樣例,但願這裏的內容能夠做爲實際開發中的參考。git

注意 這裏所展現的測試的例子是對 RESTful API 的測試,在先後端分離,構建微服務的今天,咱們在 Spring MVC 中作模板渲染的狀況愈來愈少了了,咱們主要處理的是 JSON 數據:咱們的輸入不是傳統的表單數據而是 JSON,咱們的輸出再也不是 HTML 而是 JSON。github

測試的重要性是老生常談了,但實際上並非全部的團隊都會在寫代碼的同時寫測試,在看到大量的 Spring Boot 的文章和代碼的時候竟然很難找到一個完整的、包含着測試的項目,真是恐怖。不過作了一些 search 以後我發現 Spring Boot 目前的測試真的是很是的簡單,和 Jersey 比的話那真是好的太多了。一個基本的、純粹的 Spring MVC 的測試長以下的樣子,這裏涉及多個例子,我會一點點作介紹。web

@RunWith(SpringRunner.class) // [1]
public class UsersApiTest {

    private UserRepository userRepository;

    @Before
    public void setUp() throws Exception {
        userRepository = mock(UserRepository.class);
        MockMvc mockMvc = MockMvcBuilders
                            .standaloneSetup(new UsersApi(userRepository))
                            .setControllerAdvice(new CustomizeExceptionHandler())
                            .build(); // [2]
        RestAssuredMockMvc.mockMvc(mockMvc); // [3]
    }

    @Test
    public void should_get_empty_user_lists_success() throws Exception {
        // [4]
        given().
        when().
            get("/users").
        then().
            statusCode(200);
    }

    @Test
    public void should_create_user_success() throws Exception {
        Map<String, Object> createUserParameter = new HashMap<String, Object>() {{
            put("username", "aisensiy");
        }};
        
        given() 
            .contentType("application/json")
            .body(createUserParameter)
            .when().post("/users")
            .then().statusCode(201);

        verify(userRepository).save(any()); 
    }

    @Test
    public void should_get_400_error_message_with_wrong_parameter_when_create_user() throws Exception {

        Map<String, Object> wrongParameter = new HashMap<String, Object>() {{
            put("name", "aisensiy");
        }};

        given()
            .contentType("application/json")
            .body(wrongParameter)
            .when().post("/users")
            .then().statusCode(400)
            .body("fieldErrors[0].field", equalTo("username")) // [5]
            .body("fieldErrors.size()", equalTo(1));
    }

    @Test
    public void should_get_one_user_success() throws Exception {
        User user = new User(UUID.randomUUID().toString(), "aisensiy");
        when(userRepository.findById(eq(user.getId())))
            .thenReturn(Optional.of(user));

        given()
            .standaloneSetup(new UserApi(userRepository)) 
            .when().get("/users/{userId}", user.getId()) // [6]
            .then().statusCode(200)
            .body("id", equalTo(user.getId()))
            .body("username", equalTo(user.getUsername()))
            .body("links.self", endsWith("/users/" + user.getId()));
    }
}

以上的代碼包含了四個測試用例,測試內容以下:spring

  1. GET /users 獲取用戶列表
  2. POST /users 用合法的參數建立一個用戶,返回建立成功
  3. POST /users 用非法的參數建立一個用戶,返回參數錯誤信息
  4. GET /users/{userId} 獲取單個用戶的信息

下面我按照對代碼中標註的點一個個作解釋:npm

  1. 老版本的 SpringJUnit4ClassRunner 被替換爲更容易閱讀的 SpringRunner,在 stackoverflow 中會找到大量的 SpringJUnit4ClassRunner 對我這種剛接觸的人來講真是帶來了不少的困惑。另外,咱們在這裏並無使用一個 SpringBootTest 的註解,SpringBootTest 是隻有須要一個比較完整的 Spring Boot 環境的時候(好比須要作集成測試,啓動 EmbeddedWebApplicationContext 的時候)須要。而咱們這裏僅僅經過單元測試就能夠完成任務了,這樣的好處是能夠大大提高測試的速度。
  2. MockMvcBuilders 是 Spring MVC 提供的一個 mock 環境,使咱們能夠不啓動 HTTP server 就能進行測試。這裏咱們經過 standaloneSetup 的方法建立咱們要測試的 UsersApi 而且經過 setControllerAdvice 添加錯誤處理的機制。有關 ControllerAdvice 作異常處理的內容咱們會在後面的文章中介紹。
  3. 咱們在 build.gradle 引入了 rest assured 的兩個包用於 json 的測試,咱們經過這個語句將所建立的 mock mvc 提供給 rest assured。
  4. 使用了 rest assured 的測試可讀性大大的加強了,這裏就是檢查了請求所獲取的 status code,實際的項目中可能須要作更詳細的 json 內容的測試
  5. body("fieldErrors[0].field", equalTo("username")) 這種直接讀取 json path 的測試方式相對將 json 轉化成 map 再一點點的讀取字段來講真是方便的太多,有關這種測試的其餘內容詳見 rest assured 官方文檔
  6. 這裏是一個包含動態 url 的例子,其使用方式和在 Spring MVC 中使用 PathVariable 相似

大多數狀況下,經過 standaloneSetup 的方式就能夠對 Controller 進行有效的單元測試了,固然 MockMvcBuilders 也能夠引入外部的 ControllerAdvice 對錯誤處理進行測試。加上 rest assured 測試 json api 真是簡單了太多了。不過這裏並無覆蓋 filter 的測試,後面的有關安全的文章會補上。json

最後附上項目所使用的 build.gradle,完整的項目內容能夠在 Github 找到。後端

// build.gradle
buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.flywaydb:flyway-core')
    compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
    compile("org.springframework.boot:spring-boot-starter-hateoas")
    compile('org.springframework.boot:spring-boot-starter-web')
    runtime('com.h2database:h2')
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.mybatis.spring.boot:mybatis-spring-boot-starter-test:1.3.0')
    testCompile 'io.rest-assured:rest-assured:3.0.2'
    testCompile 'io.rest-assured:spring-mock-mvc:3.0.2'
}

更多信息見 aisensiy.github.ioapi

相關文章
相關標籤/搜索