生活随笔
收集整理的這篇文章主要介紹了
SpringBoot Test及注解详解(含Mockito)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
一、版本差異
Spring Boot 2.2.0 版本開始引入 JUnit 5 作為單元測試默認庫,在 Spring Boot 2.2.0 版本之前,spring-boot-starter-test 包含了 JUnit 4 的依賴,Spring Boot 2.2.0 版本之后替換成了 Junit Jupiter。
<dependency><groupId>org.springframework.boot
</groupId><artifactId>spring-boot-starter-test
</artifactId><scope>test
</scope></dependency>
導入的依賴如下:
可以看到,SpringBootTest默認集成了以下功能:
- JUnit 5: Java單元測試框架
- Spring Test & Spring Boot Test: Spring Boot的測試工具和支持
- AssertJ: 流式斷言
- Hamcrest: Hamcrest斷言
- Mockito: Java Mock框架
- JSONassert: JSON斷言
- JsonPath: XPath for JSON
二、SpringBootTest和Junit5的使用
整體上,Spring Boot Test支持的測試種類,大致可以分為如下三類:
單元測試:一般面向方法,編寫一般業務代碼時,測試成本較大。涉及到的注解有@Test。切片測試:一般面向難于測試的邊界功能,介于單元測試和功能測試之間。涉及到的注解有 @WebMvcTest等。主要就是對于Controller的測試,分離了Service層,這里就涉及到Moc控制層所依賴的組件了功能測試:一般面向某個完整的業務功能,同時也可以使用切面測試中的mock能力,推薦使用。涉及到的注解有@SpringBootTest等。
單元測試
集成測試,不啟動server,以創建項目后自動生成的默認測試類為例:
@SpringBootTest
class TestDemoApplicationTests {@Testvoid contextLoads() {}
}
默認無參數的@SpringBootTest 注解會加載一個Web Application Context并提供Mock Web Environment,但是不會啟動內置的server。這點從日志中沒有打印Tomcat started on port(s)可以佐證。
集成測試,啟動server
新建一個測試類如下:
@SpringBootTest(webEnvironment
= SpringBootTest
.WebEnvironment
.RANDOM_PORT
)
public class DemoTest {@LocalServerPortprivate Integer port
;@Test@DisplayName("should access application")void shouldAccessApplication() {assertThat(port
).isGreaterThan(1024);}
}
也可以通過指定@SpringBootTest的Web Environment為DEFINED_PORT 來指定server偵聽應用程序配置的端口,默認為8080。不過這種指定端口的方式很少使用,因為如果本地同時啟動應用時,會導致端口沖突。
更多關系JUnit5集成SpringBootTest的例子,參考這個文檔,我這里不在啰嗦
三、Spring Boot Test中的主要注解
在說Mockito之前,先看一下SpringBootTest的注解,Mockito是一個獨立的框架,被springboot集成了而已。
從功能上講,Spring Boot Test中的注解主要分如下幾類
使用@SpringBootApplication啟動測試或者生產代碼,被@TestComponent描述的Bean會自動被排除掉。如果不是則需要向@SpringBootApplication添加TypeExcludeFilter。
- mock類型的注解
@MockBean和@SpyBean這兩個注解,在mockito框架中本來已經存在,且功能基本相同。Spring Boot Test又定義一份重復的注解,目的在于使MockBean和SpyBean被ApplicationContext管理,從而方便使用。
MockBean和SpyBean功能非常相似,都能模擬方法的各種行為。不同之處在于MockBean是全新的對象,跟正式對象沒有關系;而SpyBean與正式對象緊密聯系,可以模擬正式對象的部分方法,沒有被模擬的方法仍然可以運行正式代碼。
- 自動配置類型的注解(@AutoConfigure*)
這些注解可以搭配@\*Test使用,用于開啟在@\*Test中未自動配置的功能。例如@SpringBootTest和@AutoConfigureMockMvc組合后,就可以注入org.springframework.test.web.servlet.MockMvc。
“自動配置類型”有兩種使用方式:
在功能測試(即使用@SpringBootTest)時顯示添加。一般在切片測試中被隱式使用,例如@WebMvcTest注解時,隱式添加了@AutoConfigureCache、@AutoConfigureWebMvc、@AutoConfigureMockMvc。
所有的@*Test注解都被@BootstrapWith注解,它們可以啟動ApplicationContext,是測試的入口,所有的測試類必須聲明一個@*Test注解。
除了@SpringBootTest之外的注解都是用來進行切面測試的,他們會默認導入一些自動配置,點擊官方docs查看詳情。一般情況下,推薦使用@SpringBootTest而非其它切片測試的注解,簡單有效。若某次改動僅涉及特定切片,可以考慮使用切片測試。SpringBootTest是這些注解中最常用的一個,其中包含的配置項如下:
webEnvironment詳細說明:
- @TestComment vs @Comment:
@TestComponent是另一種@Component,在語義上用來指定某個Bean是專門用于測試的。使用@SpringBootApplication服務時,@TestComponent會被自動排除 - @TestConfiguration vs @Configuration :
@TestConfiguration是Spring Boot Boot Test提供的,@Configuration是Spring Framework提供的。@TestConfiguration實際上是也是一種@TestComponent,只是這個@TestComponent專門用來做配置用。
@TestConfiguration和@Configuration不同,它不會阻止@SpringBootTest的查找機制,相當于是對既有配置的補充或覆蓋。 - @SpringBootTest vs @WebMvcTest(或@*Test) :
都可以啟動Spring的ApplicationContext @SpringBootTest自動偵測并加載@SpringBootApplication或@SpringBootConfiguration中的配置,@WebMvcTest不偵測配置,只是默認加載一些自動配置。
@SpringBootTest測試范圍一般比@WebMvcTest大。 - @MockBean vs @SpyBean:
都能模擬方法的各種行為。不同之處在于MockBean是全新的對象,跟正式對象沒有關系;而SpyBean與正式對象緊密聯系,可以模擬正式對象的部分方法,沒有被模擬的方法仍然可以運行正式代碼
參考文章
四、Mockito的使用
簡單的一個例子
public class MyMockitoTest {private static UserServiceImpl mockUserService
;private static List
<String> mockedList
;@BeforeAllpublic static void beforeMock() throws Exception
{mockUserService
= mock(UserServiceImpl
.class);mockedList
= mock(List
.class);when(mockUserService
.getOneUser(1)).thenReturn(new User("a",1));when(mockUserService
.getOneUser(2)).thenThrow(new IllegalAccessException());when(mockUserService
.getOneUser(3)).thenReturn(new User("a",1));when(mockUserService
.update(isA(User
.class))).thenReturn(true);}@Test@DisplayName("GetOneUser")public void testGet() throws Exception
{User user
= mockUserService
.getOneUser(1);User oneUser
= mockUserService
.getOneUser(2);User oneUser1
= mockUserService
.getOneUser(3);System
.out
.println(user
);System
.out
.println(oneUser
);mockUserService
.update(user
);verify(mockUserService
, times(1)).getOneUser(eq(1));verify(mockUserService
, times(1)).update(isA(User
.class));}@Testpublic void testMatcher(){when(mockedList
.get(anyInt())).thenReturn("element");System
.out
.println(mockedList
.get(999));}@Test@DisplayName("testUsingTime")public void testUsingTime(){mockedList
.add("once");mockedList
.add("twice");mockedList
.add("twice");mockedList
.add("three times");mockedList
.add("three times");mockedList
.add("three times");verify(mockedList
).add("once");verify(mockedList
, times(1)).add("once");verify(mockedList
, times(2)).add("twice");verify(mockedList
, times(3)).add("three times");verify(mockedList
, never()).add("never happened");verify(mockedList
, atLeastOnce()).add("three times");verify(mockedList
, atLeast(2)).add("five times");verify(mockedList
, atMost(5)).add("three times");}
}
主要看一下使用mockito進行切面測試(Controller)
public class Keywords implements Serializable {private Integer id
;private String keyword
;private String notes
;public Keywords(){}@Overridepublic String
toString() {return "Keywords{" +"id=" + id
+", keyword='" + keyword
+ '\'' +", notes='" + notes
+ '\'' +'}';}public Integer
getId() {return id
;}public String
getKeyword() {return keyword
;}public String
getNotes() {return notes
;}private Keywords(Builder builder
){this.id
=builder
.id
;this.keyword
= builder
.keyword
;this.notes
= builder
.notes
;}public static class Builder{private Integer id
;private String keyword
;private String notes
;public Builder
setId(Integer id
) {this.id
= id
;return this;}public Builder
setKeyword(String keyword
) {this.keyword
= keyword
;return this;}public Builder
setNotes(String notes
) {this.notes
= notes
;return this;}public Keywords
build(){return new Keywords(this);}}
}
@Controller
public class KeywordController {@Autowiredprivate KeywordsService keywordsService
;@Autowiredprivate KeywordsServiceImpl keywordsServiceImpl
;@GetMapping(value
= "/api/keywords")public Keywords
findKeywordById(@RequestParam(value
= "id") Integer id
) {return keywordsService
.findKeywordById(id
);}@PostMapping("/api/add")@ResponseBodypublic Boolean
addOne(@RequestBody Keywords keywords
){return keywordsServiceImpl
.addOne(keywords
);}
}
@Repository
public interface KeywordsService {Keywords
findKeywordById(int i
);Boolean
addOne(Keywords keywords
);
}
@Service
public class KeywordsServiceImpl implements KeywordsService {@Overridepublic Keywords
findKeywordById(int i
) {return null
;}@Overridepublic Boolean
addOne(Keywords keywords
) {System
.out
.println("invoke spy class method");System
.out
.println(keywords
);return false;}
}
public class MvcMockitoTest {protected MockMvc mockMvc
;@Mockprivate KeywordsService keywordsService
;@Spyprivate KeywordsServiceImpl keywordsServiceImpl
;@InjectMocksprivate KeywordController controller
;@BeforeEach()public void setup() {MockitoAnnotations
.openMocks(this);mockMvc
= MockMvcBuilders
.standaloneSetup(controller
).build();}@Test@DisplayName("findKeywordByIdTest")public void findKeywordByIdTest() throws Exception
{Keywords keywords
= new Builder().setId(666).setKeyword("tester").setNotes("notes").build();Mockito
.when(keywordsService
.findKeywordById(1)).thenReturn(keywords
);MvcResult mvcResult
= mockMvc
.perform(get("/api/keywords?id=1") .contentType(MediaType
.APPLICATION_JSON
)) .andExpect(status().isOk()) .andDo(print()).andReturn();System
.out
.println(mvcResult
.getResponse().getContentAsString());}@Test@DisplayName("addOne")public void testAddOne() throws Exception
{Keywords build
= new Builder().setId(1).setKeyword("addOne").setNotes("testAddOne").build();Gson gson
= new Gson();String jsonString
=gson
.toJson(build
);System
.out
.println(jsonString
);MvcResult mvcResult
= mockMvc
.perform(MockMvcRequestBuilders
.post("/api/add").contentType(MediaType
.APPLICATION_JSON
).content(jsonString
).accept(MediaType
.APPLICATION_JSON
)).andExpect(MockMvcResultMatchers
.status().isOk()).andDo(MockMvcResultHandlers
.print()).andReturn();int status
= mvcResult
.getResponse().getStatus();assertEquals(status
,200);System
.out
.println("輸出 " + mvcResult
.getResponse().getContentAsString());}
}
結果:
mockito可以配合junit5的斷言功能使用。更多用法可以參考官方文檔
總結
以上是生活随笔為你收集整理的SpringBoot Test及注解详解(含Mockito)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。