(第三講)使用JUnit對Spring Boot中的Rest Controller進行單元測試

(第三講)使用JUnit對Spring Boot中的Rest Controller進行單元測試

本次教程主要講解如何對Spring Boot中的Rest Service進行單元測試。以往我們主要是使用JUnit對業務層進行單元測試,本次課程將使用一個簡單的案例來說明如何使用JUnit對Spring Boot的Rest Service進行單元測試。

1. 主要類容

  • 快速搭建Restfull Service 環境
  • 創建GET請求以檢索用戶信息
  • 創建GET請求檢索用戶角色信息
  • 創建POST請求新增用戶角色信息
  • 如何使用PostMan請求Restfull Service
  • 使用JUnit對GET請求進行單元測試
  • 使用JUnit對POST請求進行單元測試

2. 你將需要準備的工具

  • JDK 1.8及以上版本
  • Maven 3.0及以上版本的項目構建工具
  • IDEA代碼編輯器

3. 你可以通過以下的地址獲取本次課程的所有示例代碼

項目代碼已經上傳到GitHub倉庫中,你可以通過以下的地址獲取示例源碼:

https://github.com/ramostear/Spring_Boot_2.X_Tutorial/tree/master/spring-boot-junit-rest-service

4. 項目結構

下面通過一張截圖來了解以下本次課程中我們使用到的項目結構。

image

首先我們需要位單元測試提供一個可用的Rest Controller。UserController文件為我們提供了一個可用于測試的Rest Controller。在UserController類中,我們提供兩種請求類型的方法,一種是GET請求,另一種是POST請求。然后我們為這兩種請求方式的方法編寫單元測試用例。

在接下來的測試過程中,我們將使用Mockito來模擬請求UserService的過程,使用MockMvc來模擬請求UserController。單元測試的目的是將測試范圍盡可能的縮小。在本次案例中,我們僅對UserController中的方法進行測試。

5. 初始化項目

我們依然使用Spring Initializr來初始化本次課程的項目,你需要配置如下圖中的參數:

image

現在我們需要提供兩個實體類:User和Role:

User.java

image

Role.java

image

6. 提供可用的業務服務

所有的應用都需要有數據的存儲,本次課程主要的重點是為了Rest Controller的單元測試,因此使用ArrayList來充當數據庫的角色。在案例中,一個用戶可以有多個角色,一個角色也可以被賦予給多個用戶。用戶有ID,名字,別名和角色列表,角色具有ID,名稱和描述。在UserService類中,將提供如圖所示的公共方法。

image

7. 提供GET請求方法

在UserController類中,我們將提供如下幾個公開的GET請求方法:

  • @GetMapping(value="/users") : 獲取所有的用戶信息
  • @GetMapping(value="/users/{id}/roles") : 根據用戶ID獲取該用戶的所有角色信息

UserController.java類中的詳細代碼如下:

package com.ramostear.spring.boot.test.restservice.controller;

import com.ramostear.spring.boot.test.restservice.model.Role;
import com.ramostear.spring.boot.test.restservice.model.User;
import com.ramostear.spring.boot.test.restservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class UserController {
    private  final UserService userService;

    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
    }

    @GetMapping(value = "/users")
    public List<User> findAllStudents(){
        return userService.findAllUsers();
    }

    @GetMapping(value = "/users/{id}/roles")
    public List<Role> findUserRoles(@PathVariable(value = "id")String id){
        return userService.findUserAllRoles(id);
    }

}

8. 使用Postman對RestController進行測試

我們將使用Postman工具對上述兩個Rest API進行請求,首先向Postman地址欄輸入http://localhost:8080/users 進行測試,獲得的響應信息如下:

[
    {
        "id": "1001",
        "name": "ramostear",
        "alias": "譚朝紅",
        "roles": [
            {
                "id": "1001",
                "name": "admin",
                "description": "all permissions for this role."
            }
        ]
    }
]

下圖顯示了Postman對此API進行測試的實際結果:

image

9. 為RestController編寫單元測試

當我們需要對一個Rest Controller進行單元測試時,我們只想啟動和SpringMVC相關的組件,而不必要啟動所有的Web組件。我們可以使用WebMvcTest注解來解決這樣的測試需求。此注解將禁用Spring Boot的自動化配置,僅僅啟動與MVC相關的配置。下面將對測試用例中的幾個核心注解做一下介紹:

  • @RunWith(SpringRunner.class) : SpringRunner是SpringJUnit4ClassRunner的簡寫,它擴展了BlockJUnit4ClassRunner類,用于提供測試時的Spring應用上下文信息。
  • @WebMvcTest(value=UserController.class,secure = false) : 該注解用于測試Spring MVC應用程序,使用此注解的好處是我們只需要加載UserController類并對其中的方法進行單元測試,而不需要加載其他的控制器。
  • MockMvc : MockMvc是測試Spring MVC應用程序的主要入口,它將為我們的測試提供一個模擬的應用上下文環境。
  • @MockBean : MockBean主要是模擬向Spring應用上下文注入一個Bean對象,并使該Bean對象可以在控制器中被訪問到。

下面是測試用例的源代碼:

package com.ramostear.spring.boot.test.restservice;

import com.ramostear.spring.boot.test.restservice.controller.UserController;
import com.ramostear.spring.boot.test.restservice.model.Role;
import com.ramostear.spring.boot.test.restservice.model.User;
import com.ramostear.spring.boot.test.restservice.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.skyscreamer.jsonassert.JSONAssert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@WebMvcTest(value = UserController.class,secure = false)
public class UserControllerTests {
    private static Logger logger = LoggerFactory.getLogger(UserControllerTests.class);
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    public void findAllUsers() throws Exception{
        User user = new User();
        user.setId("1001");
        user.setName("ramostear");
        user.setAlias("譚朝紅");

        Role role = new Role();
        role.setId("1001");
        role.setName("admin");
        role.setDescription("all permissions for this role.");
        List<Role> roles = new ArrayList<>();
        roles.add(role);
        user.setRoles(roles);
        List<User> users = new ArrayList<>();
        users.add(user);

        Mockito.when(userService.findAllUsers()).thenReturn(users);
        RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/users");
        MvcResult result = mockMvc.perform(requestBuilder).andReturn();
        String expected = "[{\"id\":\"1001\",\"name\":\"ramostear\",\"alias\":\"譚朝紅\",\"roles\":[{\"id\":\"1001\",\"name\":\"admin\",\"description\":\"all permissions for this role.\"}]}]";
        logger.info(result.getResponse().getContentAsString());
        JSONAssert.assertEquals(expected,result.getResponse().getContentAsString(),false);
    }

    @Test
    public void findAllUserRoles() throws Exception{
        Role role = new Role();
        role.setId("1001");
        role.setName("admin");
        role.setDescription("all permissions for this role.");
        List<Role> roles = new ArrayList<>();
        roles.add(role);
        Mockito.when(userService.findUserAllRoles("1001")).thenReturn(roles);
        RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/users/1001/roles");
        MvcResult result = mockMvc.perform(requestBuilder).andReturn();
        String expected = "[{\"id\":\"1001\",\"name\":\"admin\",\"description\":\"all permissions for this role.\"}]";
       logger.info(result.getResponse().getContentAsString());
        JSONAssert.assertEquals(expected,result.getResponse().getContentAsString(),false);
    }
}

Mockito.when().thenReturn():用于測試UserService在被調用時是否返回和預期一致的結果

mockMvc.perform().andReturn():mockMvc主要用于執行請求并返回響應數據

下面我們執行上述兩個方法,看看測試結果:

image

兩個方法均測試通過,且控制臺也輸出了如下的日志信息:

2019-05-10 05:36:40.567  INFO 18268 --- [           main] c.r.s.b.t.r.UserControllerTests          : [{"id":"1001","name":"admin","description":"all permissions for this role."}]
2019-05-10 05:36:40.585  INFO 18268 --- [           main] c.r.s.b.t.r.UserControllerTests          : [{"id":"1001","name":"ramostear","alias":"譚朝紅","roles":[{"id":"1001","name":"admin","description":"all permissions for this role."}]}]

10 . 添加POST請求方法

現在我們在Rest Controller中新增一個為用戶添加新角色的方法,當角色被成功設置后將返回狀態碼為201的一個創建資源狀態。代碼如下:

 @PostMapping(value = "/users/{id}")
    public ResponseEntity<Object> setUserRole(@PathVariable(value = "id")String id, @RequestBody Role role){
        userService.addUserRole(id,role);
        return new ResponseEntity<>(role, HttpStatus.CREATED);
    }

使用Postman對此接口進行請求,獲得如下的響應信息:

{
    "id": "1002",
    "name": "editor",
    "description": "content editor"
}

下圖時使用Postman請求的實際結果:

image

11. 為Post方法提供單元測試用例

在接下來的測試中,我們將使用MockMvcRequestBuilders.post()方法來模擬請求添加用戶角色的方法,并使用accept()方法來設置數據格式,另外還需斷言請求響應的狀態值是否為CREATED且返回的角色信息是否與預期的一致。Post方法的測試源碼如下:

@Test
    public void addUserRole() throws Exception{
        String JSON = "{\"id\":\"1002\",\"name\":\"editor\",\"description\":\"content editor\"}";
        RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/users/1001")
                .accept(MediaType.APPLICATION_JSON).content(JSON)
                .contentType(MediaType.APPLICATION_JSON);
        MvcResult result = mockMvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = result.getResponse();
        Assert.assertEquals(HttpStatus.CREATED.value(),response.getStatus());
        String expected = "{\"id\":\"1002\",\"name\":\"editor\",\"description\":\"content editor\"}";
        logger.info(result.getResponse().getContentAsString());
        JSONAssert.assertEquals(expected,result.getResponse().getContentAsString(),false);
    }

最后,我們執行此測試用例方法,觀察測試結構:

image

通過圖我們可以看到,Post方法已成功通過測試。

今天的課程分享到這里就結束了,在本次課程中,給出了如何測試Rest Controller的方法,同時還使用Postman來進行輔助測試。所有的測試都達到了預期的測試效果。

作者:譚朝紅
鏈接:(第三講)使用JUnit對Spring Boot中的Rest Controller進行單元測試
來源:譚朝紅的技術分享博客

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容