spring-boot-test詳解

一、基本介紹

spring-boot-test是SpringBoot的一個功能特性,對衆多的單元測試技術進行了集成,我們可以通過在項目中添加下面的依賴引入這項特性:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

其中exclusions的目的只是爲了排除junit4而採用Junit5,可根據自己使用的junit版本來確認是否要配置這一項。
當我們依賴了spring-boot-starter-test之後,會自動的引入以下幾種常見的單元測試技術:

  • junit5
  • Mockito
  • Hamcrest
  • AssertJ
  • JSONassert
  • JsonPath
  • Spring Test(用於SpringBoot項目的測試)

本篇會對常用的功能進行講解,如果想了解所有功能,請來這裏:【spring-boot-test官網

二、測試Bean

對於普通的JAVA代碼的測試,通過Junit和Mockito就可以完成,這裏我們看看怎麼測試spring容易中的Bean,比如:Service。

1. 常用註解

  • @SpringBootTest:表明這是一個springboot的測試用例,默認情況下,是不會開啓一個服務的,可以通過配置webEnvironment來改變運行模式;
  • @MockBean:mock出一個javabean,注入到spring容器中,會替代原始Spring容器中對應的Bean。
  • @SpyBean:對容易中已有的Bean進行包裝,構建一個監控對象,這樣的話,如果沒有進行mock的時候,會走真實方法。

2. 示例

Service代碼:

package com.firewolf.busi.example.springtest;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public void addUser(User user) {
        if (StringUtils.isEmpty(user.getAccount()) || user.getAccount().length() < 4) {
            throw new RuntimeException("account is too short");
        }
        int i = userMapper.selectCount(User.builder().account(user.getAccount()).build());
        if (i > 0) {
            throw new RuntimeException("account can not repeat");
        }
        userMapper.insertSelective(user);
    }

    public List<User> selectUser() {
        return userMapper.selectAll();
    }
}

    ....

我們現在想測試addUser方法的邏輯是否正確,而我們又不需要訪問數據庫,這個時候,我們可以Mock出UserMapper,來測試addUser的邏輯
測試代碼:

@SpringBootTest
class UserServiceTest {
    @Autowired
    private UserService userService;
    //我們不關心UserMapper的邏輯,直接mock出來
    @MockBean
    private UserMapper userMapper;
    @Test
    void addUser() {
        assertThrows(RuntimeException.class, () -> userService.addUser(User.builder().name("周扒皮").password("1111").build()), "account is too short");
        assertThrows(RuntimeException.class, () -> userService.addUser(User.builder().account("abc").name("周扒皮").password("1111").build()), "account is too short");

        String name = "myname";
        when(userMapper.selectCount(eq(User.builder().account(name).build()))).thenReturn(1);
        assertThrows(RuntimeException.class, () -> userService.addUser(User.builder().account(name).build()));
        assertDoesNotThrow(() -> userService.addUser(User.builder().account("wangba").build()));
    }

如果這個UserMapper不是mock出來的,那麼,就會走自己的真實邏輯。
可以看到,其他的地方的寫法,和junit是類似的。

三、測試web

有時候,我們需要測試我們的web controller,來判斷我們的請求是否能正常處理

1. 核心註解

  • @AutoConfigureMockMvc:自動裝配mockmvc,來進行web方法的測試,一般用於測試controller;然後我們就可以直接注入MockMvc來模擬發起web請求了;

2. 核心API

  • RequestBuilder/MockMvcRequestBuilders:
    //根據uri模板和uri變量值得到一個GET請求方式的MockHttpServletRequestBuilder;
    MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables)
    //同get類似,但是是POST方法;
    MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables)
    //同get類似,但是是PUT方法;
    MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables)
    //同get類似,但是是DELETE方法;
    MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables)
    //同get類似,但是是OPTIONS方法;
    MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables)
    //提供自己的Http請求方法及uri模板和uri變量,如上API都是委託給這個API;
    MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables)
    //提供文件上傳方式的請求,得到MockMultipartHttpServletRequestBuilder;
    MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables)
    //創建一個從啓動異步處理的請求的MvcResult進行異步分派的RequestBuilder;
    RequestBuilder asyncDispatch(final MvcResult mvcResult)
    
  • MockHttpServletRequestBuilder
    //:添加頭信息;
    MockHttpServletRequestBuilder header(String name, Object... values)/MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders)
    //:指定請求的contentType頭信息;
    MockHttpServletRequestBuilder contentType(MediaType mediaType)
    //:指定請求的Accept頭信息;
    MockHttpServletRequestBuilder accept(MediaType... mediaTypes)/MockHttpServletRequestBuilder accept(String... mediaTypes)
    //:指定請求Body體內容;
    MockHttpServletRequestBuilder content(byte[] content)/MockHttpServletRequestBuilder content(String content)
    //:請求傳入參數
    MockHttpServletRequestBuilder param(String name,String... values)
    //:指定請求的Cookie;
    MockHttpServletRequestBuilder cookie(Cookie... cookies)
    //:指定請求的Locale;
    MockHttpServletRequestBuilder locale(Locale locale)
    //:指定請求字符編碼;
    MockHttpServletRequestBuilder characterEncoding(String encoding)
    //:設置請求屬性數據;
    MockHttpServletRequestBuilder requestAttr(String name, Object value) 
    //:設置請求session屬性數據;
    MockHttpServletRequestBuilder sessionAttr(String name, Object value)/MockHttpServletRequestBuilder sessionAttrs(Map<string, object=""> sessionAttributes)
    //指定請求的flash信息,比如重定向後的屬性信息;
    MockHttpServletRequestBuilder flashAttr(String name, Object value)/MockHttpServletRequestBuilder flashAttrs(Map<string, object=""> flashAttributes)
    //:指定請求的Session;
    MockHttpServletRequestBuilder session(MockHttpSession session) 
    // :指定請求的Principal;
    MockHttpServletRequestBuilder principal(Principal principal)
    //:指定請求的上下文路徑,必須以“/”開頭,且不能以“/”結尾;
    MockHttpServletRequestBuilder contextPath(String contextPath) 
    //:請求的路徑信息,必須以“/”開頭;
    MockHttpServletRequestBuilder pathInfo(String pathInfo) 
    //:請求是否使用安全通道;
    MockHttpServletRequestBuilder secure(boolean secure)
    //:請求的後處理器,用於自定義一些請求處理的擴展點;
    MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor)
    
  • MockMultipartHttpServletRequestBuilder
    //:指定要上傳的文件;
    MockMultipartHttpServletRequestBuilder file(String name, byte[] content)/MockMultipartHttpServletRequestBuilder file(MockMultipartFile file)
    
  • ResultActions
    //:添加驗證斷言來判斷執行請求後的結果是否是預期的;
    ResultActions andExpect(ResultMatcher matcher) 
    //:添加結果處理器,用於對驗證成功後執行的動作,如輸出下請求/結果信息用於調試;
    ResultActions andDo(ResultHandler handler) 
    //:返回驗證成功後的MvcResult;用於自定義驗證/下一步的異步處理;
    MvcResult andReturn()
    
  • ResultMatcher/MockMvcResultMatchers
    //:請求的Handler驗證器,比如驗證處理器類型/方法名;此處的Handler其實就是處理請求的控制器;
    HandlerResultMatchers handler()
    //:得到RequestResultMatchers驗證器;
    RequestResultMatchers request()
    //:得到模型驗證器;
    ModelResultMatchers model()
    //:得到視圖驗證器;
    ViewResultMatchers view()
    //:得到Flash屬性驗證;
    FlashAttributeResultMatchers flash()
    //:得到響應狀態驗證器;
    StatusResultMatchers status()
    //:得到響應Header驗證器;
    HeaderResultMatchers header()
    //:得到響應Cookie驗證器;
    CookieResultMatchers cookie()
    //:得到響應內容驗證器;
    ContentResultMatchers content()
    //:得到Json表達式驗證器;
    JsonPathResultMatchers jsonPath(String expression, Object ... args)/ResultMatcher jsonPath(String expression, Matcher matcher)
    //:得到Xpath表達式驗證器;
    XpathResultMatchers xpath(String expression, Object... args)/XpathResultMatchers xpath(String expression, Map<string, string=""> namespaces, Object... args)
    //:驗證處理完請求後轉發的url(絕對匹配);
    ResultMatcher forwardedUrl(final String expectedUrl)
    //:驗證處理完請求後轉發的url(Ant風格模式匹配,@since spring4);
    ResultMatcher forwardedUrlPattern(final String urlPattern)
    //:驗證處理完請求後重定向的url(絕對匹配);
    ResultMatcher redirectedUrl(final String expectedUrl)
    //:驗證處理完請求後重定向的url(Ant風格模式匹配,@since spring4);
    ResultMatcher redirectedUrlPattern(final String expectedUrl)
    

3. 使用案例

package com.firewolf.busi.example.springtest;

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.boot.test.mock.mockito.SpyBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;

    @Test
    void addUser() throws Exception {
        doNothing().when(userService).addUser(any(User.class));
        MvcResult mockResult = mockMvc
                .perform(post("/users").contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .param("account","liuxing").param("password","123345")) //傳入參數
                .andExpect(status().isOk()) // 斷言狀態
                .andReturn(); //返回結果
        assertEquals("success",mockResult.getResponse().getContentAsString()); //斷言結果
    }

}

4. 使用JsonPath解析結果並進行斷言

我們可以使用jsonpath來解析結果並進行斷言,關於jsonpath的使用,可以來這裏:【JsonPath官網
示例:

package com.firewolf.busi.example.springtest;

import org.hamcrest.MatcherAssert;
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.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserService userService;
    @Test
    void testSelectUser() throws Exception {
        List<User> users = Arrays.asList(
                User.builder().account("lx").build(),
                User.builder().account("liuxing").build()
        );

        doReturn(users).when(userService).selectUser();

        //使用JsonPath解析結果並進行斷言
        mockMvc.perform(get("/users"))
                .andExpect(jsonPath("$.size()").value(2))
                .andExpect(jsonPath("$..account").isArray())
                .andExpect(jsonPath("$.[0].account").value("lx"))
                .andDo(MockMvcResultHandlers.print(System.out))
                .andDo(mvcResult -> assertTrue(mvcResult.getResponse().getContentAsString().contains("liuxing")));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章