本節介紹Spring應用程序的集成測試。
一、概述
能夠在不需要部署到應用程序服務器或連接到其他企業基礎設施的情況下執行某些集成測試是很重要的。這樣做可以測試以下內容:
- spring IoC容器上下文的正確連接。
- 使用JDBC或ORM工具進行數據訪問。這可以包括SQL語句的正確性、Hibernate查詢、JPA實體映射等等。
Spring框架為Spring測試模塊中的集成測試提供了一流的支持。此庫包括org.springframework.test包(常常結合Junit框架一起使用),其中包含用於與Spring容器集成測試的有價值的類。
此測試不依賴於應用程序服務器或其他部署環境。此類測試的運行速度比單元測試慢,但比依賴於部署到應用服務器的等效Selenium測試或遠程測試快得多。
二、JDBC測試支持
org.springframework.test.jdbc包含JdbcTestUtils,它是jdbc相關實用程序函數的集合,旨在簡化標准數據庫測試場景。具體來說,JdbcTestUtils提供了以下靜態實用程序方法。
- countRowsTable(..):計算給定表中的行數。
- countRowsTableWhere(..):使用提供的WHERE子句計算給定表中的行數。
- deleteFromTables(..):刪除指定表中的所有行。
- deleteFromTableWhere(..):使用提供的WHERE子句從給定表中刪除行。
- dropTables(..):刪除指定的表。
AbstractTransactionalJUnit4SpringContextTests 和 AbstractTransactionalTestNGSpringContextTests提供了委托給JdbcTestUtils中上述方法的方便方法。
spring jdbc模塊提供對配置和啟動嵌入式數據庫的支持,您可以在與數據庫交互的集成測試中使用它。
三、注解
本小節介紹測試Spring應用程序時可以使用的注解。
Spring測試框架的注解
@BootstrapWith
@BootstrapWith是一個類級別的注釋,可以用來配置Spring測試框架的引導方式。具體來說,您可以使用@BootstrapWith來指定一個自定義的TestContextBootstrapper。
@ContextConfiguration
@ContextConfiguration定義類級元數據,用於確定如何為集成測試加載和配置ApplicationContext。具體來說,@ContextConfiguration聲明應用程序上下文資源位置(xml)或用於加載上下文的組件類(@configuration類)。
@ContextConfiguration("/test-config.xml")
public class XmlApplicationContextTests {
// class body...
}
或者
@ContextConfiguration(classes = TestConfig.class) public class ConfigClassApplicationContextTests { // class body... }
除了聲明資源位置或組件類之外,您還可以使用@ContextConfiguration來聲明ApplicationContextInitializer類。
@ContextConfiguration(initializers = CustomContextIntializer.class) public class ContextInitializerTests { // class body... }
@WebAppConfiguration
@WebAppConfiguration是一個類級別的注釋,你可以使用它來聲明為集成測試加載的ApplicationContext應該是WebApplicationContext。只要測試類上存在@WebAppConfiguration,就可以使用默認值為測試加載WebApplicationContext。
src/main/webapp“指向web應用程序根目錄的路徑(即資源基路徑)。資源基路徑在后台用於創建MockServletContext,它充當測試的WebApplicationContext的ServletContext。
@ContextConfiguration @WebAppConfiguration public class WebAppTests { // class body... }
你可以使用不同的默認屬性來指定基路徑。同時支持classpath:和file:resource前綴。如果沒有提供資源前綴,則假定路徑是文件系統資源。
@ContextConfiguration @WebAppConfiguration("classpath:test-web-resources") public class WebAppTests { // class body... }
@ContextHierarchy
@ContextHierarchy是一個類級別的注釋,用於為集成測試定義ApplicationContext實例的層次結構。@ContextHierarchy應該用一個或多個@ContextConfiguration實例的列表來聲明,每個實例都定義了上下文層次結構中的一個級別。
@ContextHierarchy({ @ContextConfiguration("/parent-config.xml"), @ContextConfiguration("/child-config.xml") }) public class ContextHierarchyTests { // class body... }
或者
@WebAppConfiguration @ContextHierarchy({ @ContextConfiguration(classes = AppConfig.class), @ContextConfiguration(classes = WebConfig.class) }) public class WebIntegrationTests { // class body... }
如果需要合並或重寫測試類層次結構中給定級別的上下文層次結構的配置,則必須通過在類層次結構中每個相應級別的@ContextConfiguration中的name屬性提供相同的值來顯式地命名該級別。
@ContextHierarchy({ @ContextConfiguration(name = "parent", locations = "/app-config.xml"), @ContextConfiguration(name = "child", locations = "/user-config.xml") })public class ExtendedTests{
}
@ActiveProfiles
@ActiveProfiles是一個類級別的注釋,用於聲明在為集成測試加載ApplicationContext時哪些bean定義的Profile應該是被激活的。
@ContextConfiguration @ActiveProfiles({"dev", "integration"}) public class DeveloperIntegrationTests { // class body... }
@TestPropertySource
@TestPropertySource是一個類級注解,可用於配置要添加到為集成測試加載的ApplicationContext的環境中的Properties配置文件或屬性。
@ContextConfiguration @TestPropertySource("/test.properties") public class MyIntegrationTests { // class body... }
或者
@ContextConfiguration @TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) public class MyIntegrationTests { // class body... }
@DirtiesContext
@TestExecutionListeners
@Commit
@Commit表示事務性測試方法的事務應在測試方法完成后提交。
@Commit @Test public void testProcessWithoutRollback() { // ... }
@Rollback
@Rollback表示是否應在測試方法完成后回滾事務測試方法的事務。如果為true,則回滾事務。否則,事務被提交。spring測試框架中集成測試的回滾默認為true,即使沒有顯式聲明@Rollback。
當聲明為類級別的注釋時,@Rollback為測試類層次結構中的所有測試方法定義默認的回滾語義。當聲明為方法級注釋時,@Rollback為特定的測試方法定義回滾語義,可能會覆蓋類級別的@Rollback或@Commit語義。
@Rollback(false) @Test public void testProcessWithoutRollback() { // ... }
@BeforeTransaction
@BeforeTransaction表示對於已配置為@Transactional注釋在事務內運行的測試方法,應在啟動事務之前運行帶注釋的void方法。從SpringFramework4.3開始,@BeforeTransaction方法不需要是公共的,可以在基於Java8的接口默認方法上聲明。
@BeforeTransaction void beforeTransaction() { // logic to be executed before a transaction is started }
@AfterTransaction
@AfterTransaction表示,對於已配置為使用Spring的@Transactional注釋在事務內運行的測試方法,應在事務結束后運行帶注釋的void方法。從springframework4.3開始,@AfterTransaction方法不需要是公共的,可以在基於java8的接口默認方法上聲明。
@AfterTransaction void afterTransaction() { // logic to be executed after a transaction has ended }
@Sql
@Sql用於注釋測試類或測試方法,以配置在集成測試期間針對給定數據庫運行的Sql腳本。
@Test @Sql({"/test-schema.sql", "/test-user-data.sql"}) public void userTest { // execute code that relies on the test schema and test data }
@SqlConfig
@SqlGroup
標准注解支持
spring測試框架的所有配置都支持以下注解。注意,這些注解不是特定於測試的,可以在Spring框架中的任何地方使用。
@Autowired
@Qualifier
@Resource (javax.annotation):JSR-250
@ManagedBean (javax.annotation):JSR-250
@Inject (javax.inject):JSR-330
@Named (javax.inject):JSR-330
@PersistenceContext (javax.persistence) :JPA
@PersistenceUnit (javax.persistence) :JPA
@Required
@Transactional
單元測試時,如果在@Test的方法體內存在對數據庫進行非查詢的操作,會自動回滾,因為它會自動帶@Rollback(true)。所以存在對數據庫操作的,建議不要在測試類上定義,還是在service或dao這種普通bean里定義。
Spring JUnit 4注解
以下注解僅在與SpringRunner、Spring的JUnit4規則或Spring的JUnit4支持類一起使用時才受支持:
@RunWith
首先要分清幾個概念:測試方法、測試類、測試集、測試運行器。
其中測試方法就是用@Test注解的一些函數。測試類是包含一個或多個測試方法的一個xxxTest.java文件。測試集是一個suite,可能包含多個測試類。測試運行器則決定了用什么方式偏好去運行這些測試集/類/方法。
而@Runwith就是放在測試類名之前,用來確定這個類怎么運行的。也可以不標注,會使用默認運行器。
@RunWith(Parameterized.class) ,參數化運行器,配合@Parameters使用junit的參數化功能。
@RunWith(JUnit4.class),junit4的默認運行器。
@RunWith(JUnit38ClassRunner.class),用於兼容junit3.8的運行器
@RunWith(SpringJUnit4ClassRunner.class),讓測試運行於Spring測試環 境,以便在測試開始的時候自動創建Spring的應用上下文。
@RunWith(SpringRunner.class):因為SpringRunner.class繼承了SpringJUnit4ClassRunner.class且沒有進行任何修改,所以@RunWith(SpringRunner.class)基本等同於@RunWith(SpringJUnit4ClassRunner.class)。
@RunWith(Suite.class)
@SuiteClasses({ATest.class,BTest.class,CTest.class}) 測試集運行器配合使用測試集功能
注:如果測試類中無此注解,將導致Spring中的service,dao等自動注入失敗。
@Test
聲明為要測試的方法。
注意:測試方法必須是public void,即公共、無返回數據。可以拋出異常。
@Ignore
有時候我們想暫時不運行某些測試方法\測試類,可以在方法前加上這個注解。在運行結果中,junit會統計忽略的用例數,來提醒你。但是不建議經常這么做,因為這樣的壞處時,容易忘記去更新這些測試方法,導致代碼不夠干凈,用例遺漏。
@BeforeClass
當我們運行幾個有關聯的用例時,可能會在數據准備或其它前期准備中執行一些相同的命令,這個時候為了讓代碼更清晰,更少冗余,可以將公用的部分提取出來,放在一個方法里,並為這個方法注解@BeforeClass。意思是在測試類里所有用例運行之前,運行一次這個方法。例如創建數據庫連接、讀取文件等。
注意:方法名可以任意,但必須是public static void,即公開、靜態、無返回。這個方法只會運行一次。
@AfterClass
跟@BeforeClass對應,在測試類里所有用例運行之后,運行一次。用於處理一些測試后續工作,例如清理數據,恢復現場。
注意:同樣必須是public static void,即公開、靜態、無返回。這個方法只會運行一次。
@Before
與@BeforeClass的區別在於,@Before不止運行一次,它會在每個用例運行之前都運行一次。主要用於一些獨立於用例之間的准備工作。比如兩個用例都需要讀取數據庫里的用戶A信息,但第一個用例會刪除這個用戶A,而第二個用例需要修改用戶A。那么可以用@BeforeClass創建數據庫連接。用@Before來插入一條用戶A信息。
注意:必須是public void,不能為static。不止運行一次,根據用例數而定。
@After
與@Before對應。
@IfProfileValue
@IfProfileValue表示已為特定測試環境啟用帶注釋的測試。如果配置的ProfileValueSource為提供的名稱返回匹配的值,則啟用測試。否則,測試將被禁用,並且實際上被忽略。
@IfProfileValue(name="java.vendor", value="Oracle Corporation") @Test public void testProcessWhichRunsOnlyOnOracleJvm() { // some logic that should run only on Java VMs from Oracle Corporation }
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration是一個類級注解,它指定在檢索通過@IfProfileValue注解配置的配置文件值時要使用的ProfileValueSource類型。如果沒有為測試聲明@ProfileValueSourceConfiguration,則默認使用SystemProfileValueSource。
@ProfileValueSourceConfiguration(CustomProfileValueSource.class) public class CustomProfileValueSourceTests { // class body... }
@Timed
@Timed表示帶注釋的測試方法必須在指定的時間段(毫秒)內完成執行。如果文本執行時間超過指定的時間段,則測試失敗。
時間段包括測試方法本身的運行、測試的任何重復,以及測試類的任何設置或拆卸。
@Timed(millis = 1000) public void testProcessWithOneSecondTimeout() { // some logic that should not take longer than 1 second to execute }
@Repeat
@Repeat表示帶注釋的測試方法必須重復運行。在注釋中指定要執行測試方法的次數。
重復執行的范圍包括測試方法本身的執行以及測試夾具的任何設置或拆卸。
@Repeat(10) @Test public void testProcessRepeatedly() { // ... }
Spring JUnit Jupiter(Junit5)
以下注釋僅在與SpringExtension和JUnit Jupiter(即JUnit 5中的編程模型)結合使用時才受支持:
@SpringJUnitConfig
@SpringJUnitWebConfig
@EnabledIf
@DisabledIf
四、Spring TestContext框架
概要介紹
Spring TestContext框架(位於org.springframework.test.context包)提供一般的、注釋驅動的單元和集成測試支持,這些支持與所使用的測試框架無關。TestContext框架還非常重視約定優先於配置,合理的默認值可以通過基於注釋的配置覆蓋。
除了通用的測試基礎設施之外,TestContext框架還為junit4、junit jupiter(又名junit5)和TestNG提供了明確的支持。對於junit4和TestNG,Spring提供了抽象的支持類。此外,Spring為junit4提供了一個定制的JUnit運行器和定制的JUnit規則,並為junit jupiter提供了一個自定義擴展,允許您編寫所謂的POJO測試類。POJO測試類不需要擴展特定的類層次結構,比如抽象支持類。
框架的核心由TestContextManager類和TestContext、TestExecutionListener和SmartContextLoader接口組成。為每個測試類創建一個TestContextManager。反過來,TestContextManager管理保存當前測試上下文的TestContext。TestContextManager還隨着測試的進行更新TestContext的狀態,並委托給TestExecutionListener實現,后者通過提供依賴注入、管理事務等來檢測實際的測試執行。SmartContextLoader負責為給定的測試類加載ApplicationContext
TestContext
TestContext封裝執行測試的上下文(與實際使用的測試框架無關),並為它負責的測試實例提供上下文管理和緩存支持。TestContext還委托SmartContextLoader加載ApplicationContext(如果請求)。
TestContextManager
TestContextManager是Spring TestContext框架的主要入口點,負責管理單個TestContext,並在定義良好的測試執行點向每個注冊的TestExecutionListener發送事件。
TestExecutionListener
TestExecutionListener定義用於響應由TestContextManager發布的測試執行事件的API,偵聽器是用該API注冊的。
Context Loaders
ContextLoader是一個策略接口,用於為SpringTestContext框架管理的集成測試加載ApplicationContext。你應該實現SmartContextLoader而不是這個接口來提供對組件類、bean配置文件、屬性或屬性文件、上下文層次結構和WebApplicationContext支持的支持。
Spring提供了以下實現:
- DelegatingSmartContextLoader:兩個默認加載程序之一,它在內部委托給AnnotationConfigContextLoader、GenericXmlContextLoader或GenericGroovyXmlContextLoader,這取決於為測試類聲明的配置或是否存在默認位置或默認配置類。只有在Groovy位於類路徑上時,才啟用Groovy支持。
- WebDelegatingSmartContextLoader:兩個默認加載程序之一,它在內部委托給AnnotationConfigWebContextLoader、GenericXmlWebContextLoader或GenericGroovyXmlWebContextLoader,這取決於為測試類聲明的配置,或者默認位置或默認配置類的存在。僅當測試類上存在@WebAppConfiguration時才使用web ContextLoader。只有在Groovy位於類路徑上時,才啟用Groovy支持。
- AnnotationConfigContextLoader:從組件類加載標准的ApplicationContext。
- AnnotationConfigWebContextLoader:從組件類加載WebApplicationContext。
- GenericGroovyXmlContextLoader:從Groovy腳本或XML配置文件的資源位置加載標准的ApplicationContext。
- GenericGroovyXmlWebContextLoader:從資源位置加載WebApplicationContext,這些資源位置可以是Groovy腳本或XML配置文件。
- GenericXmlContextLoader:從XML資源位置加載標准的ApplicationContext。
- GenericXmlWebContextLoader:從XML資源位置加載WebApplicationContext。
- GenericPropertiesContextLoader:從Java屬性文件加載標准的ApplicationContext。
上下文管理
每個TestContext為它負責的測試實例提供上下文管理和緩存支持。測試實例不會自動接收對配置的ApplicationContext的訪問。但是,如果測試類實現ApplicationContextAware接口,則會向測試實例提供對ApplicationContext的引用。注意,AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests實現了ApplicationContextAware,因此可以自動提供對ApplicationContext的訪問。
使用TestContext框架的測試類不需要擴展任何特定的類或實現特定的接口來配置它們的應用程序上下文。相反,配置是通過在類級別聲明@ContextConfiguration注釋來實現的。如果測試類沒有顯式聲明應用程序上下文資源位置或組件類,則配置的ContextLoader將確定如何從默認位置或默認配置類加載上下文。除了上下文資源位置和組件類之外,還可以通過應用程序上下文初始化器配置應用程序上下文。
下面解釋如何使用Spring的@ContextConfiguration注釋,通過使用XML配置文件、Groovy腳本、組件類(通常是@configuration)或上下文初始化器來配置測試應用程序context。或者,您可以為高級用例實現和配置自己的自定義SmartContextLoader。
使用XML資源配置上下文
要使用XML配置文件為測試加載ApplicationContext,請使用@ContextConfiguration注釋測試類,並使用包含XML配置元數據的資源位置的數組配置locations屬性。普通路徑或相對路徑(例如,上下文.xml)被視為相對於在其中定義測試類的包的類路徑資源。以斜杠開頭的路徑被視為絕對類路徑位置(例如,/org/example/配置.xml). 表示資源URL的路徑(例如,前綴為classpath:、file:、http:等的路徑)按原樣使用。
@RunWith(SpringRunner.class) // ApplicationContext will be loaded from "/app-config.xml" and // "/test-config.xml" in the root of the classpath @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) public class MyTest { // class body... }
如果從@ContextConfiguration注釋中省略了locations和value屬性,TestContext框架將嘗試檢測默認的XML資源位置。具體來說,GenericXmlContextLoader和GenericXmlWebContextLoader根據測試類的名稱檢測默認位置。如果你的類被命名com.example.MyTest從您的應用程序ContextLoader加載GenericXmlContext類路徑:com/example/MyTest-context.xml。
組件類的上下文配置
要使用組件類為測試加載ApplicationContext,可以使用@ContextConfiguration注釋測試類,並使用包含對組件類的引用的數組配置classes屬性(一般是@Configuration注釋的類)。
@RunWith(SpringRunner.class) // ApplicationContext will be loaded from AppConfig and TestConfig @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) public class MyTest { // class body... }
如果從@ContextConfiguration注釋中省略classes屬性,TestContext框架將嘗試檢測是否存在默認配置類。具體來說,AnnotationConfigContextLoader和AnnotationConfigWebContextLoader檢測滿足配置類實現要求的測試類的所有靜態嵌套類,如@configuration中指定的。請注意,配置類的名稱是任意的。此外,如果需要,測試類可以包含多個靜態嵌套配置類。在以下示例中,OrderServiceTest類聲明一個名為Config的靜態嵌套配置類,該類自動用於加載測試類的ApplicationContext:
@RunWith(SpringRunner.class) // ApplicationContext will be loaded from the // static nested Config class @ContextConfiguration public class OrderServiceTest { @Configuration static class Config { // this bean will be injected into the OrderServiceTest class @Bean public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); // set properties, etc. return orderService; } } @Autowired private OrderService orderService; @Test public void testOrderService() { // test the orderService } }
加載WebApplicationContext
Spring3.2引入了在集成測試中加載WebApplicationContext的支持。要指示TestContext框架加載WebApplicationContext而不是標准的ApplicationContext,可以使用@WebAppConfiguration注釋相應的測試類。
測試類上的@WebAppConfiguration指示TestContext框架(TCF)應該為集成測試加載WebApplicationContext(WAC)。在后台,TCF確保創建MockServletContext並將其提供給測試的WAC。默認情況下,MockServletContext的基本資源路徑設置為src/main/webapp。這被解釋為相對於JVM根的路徑(通常是項目的路徑)。如果您熟悉Maven項目中web應用程序的目錄結構,就知道src/main/webapp是WAR根目錄的默認位置。如果需要覆蓋此默認值,可以提供指向@WebAppConfiguration注釋的備用路徑(例如,@WebAppConfiguration(“src/test/webapp”))。如果希望從類路徑而不是文件系統引用基資源路徑,可以使用Spring的classpath:prefix。
@RunWith(SpringRunner.class) // defaults to "file:src/main/webapp" @WebAppConfiguration // detects "WacTests-context.xml" in the same package // or static nested @Configuration classes @ContextConfiguration public class WacTests { //... }
或者
@RunWith(SpringRunner.class) // classpath resource @WebAppConfiguration("classpath:test-web-resources") // file system resource @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") public class WacTests { //... }
五、Spring MVC測試框架
springmvc測試框架為測試springmvc代碼提供了一流的支持,該API可以與JUnit、TestNG或任何其他測試框架一起使用。它構建在spring測試模塊的servletapi模擬對象上,因此不使用正在運行的Servlet容器。它使用DispatcherServlet提供完整的SpringMVC運行時行為,並支持使用TestContext框架加載實際的Spring配置,此外還提供獨立模式,在這種模式下,您可以手動實例化控制器並一次測試一個控制器。
springmvc測試還為使用ResTemplate的測試代碼提供客戶端支持。客戶端測試模擬服務器響應,也不使用正在運行的服務器。
服務器端測試
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringJUnitWebConfig(locations = "test-servlet-context.xml") class ExampleTests { private MockMvc mockMvc; @BeforeEach void setup(WebApplicationContext wac) { this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); } @Test void getAccount() throws Exception { this.mockMvc.perform(get("/accounts/1") .accept(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(status().isOk()) .andExpect(content().contentType("application/json")) .andExpect(jsonPath("$.name").value("Lee")); } }
MockMvc實例用於執行對/accounts/1的GET請求,並驗證結果響應的狀態為200,內容類型為application/json,並且響應主體具有一個名為name的json屬性,值為Lee。
靜態導入
上一節示例中需要一些靜態導入,例如MockMvcRequestBuilders.*、MockMvcResultMatchers.*和MockMvcBuilders.*。找到這些類的一個簡單方法是搜索匹配MockMvc*的類型。
設置選項
創建MockMvc實例有兩個主要選項。第一種方法是通過TestContext框架加載springmvc配置,該框架加載Spring配置並向測試注入一個WebApplicationContext來構建MockMvc實例。
@RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration("my-servlet-context.xml") public class MyWebTests { @Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } // ... }
第二種是在不加載Spring配置的情況下手動創建一個控制器實例。
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); } // ... }
推薦第一種的方式創建MockMvc實例。
設置功能
無論您使用哪種MockMvc構建器,所有MockMvcBuilder實現都提供了一些常見且非常有用的特性。例如,您可以為所有請求聲明一個Accept頭,並期望狀態為200,同時在所有響應中聲明一個Content-Type頭:
// static import of MockMvcBuilders.standaloneSetup MockMvc mockMvc = standaloneSetup(new MusicController()) .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build();
此外,第三方框架(和應用程序)可以預先打包安裝指令,例如MockMvcConfigurer中的指令。Spring框架有一個這樣的內置實現,可以幫助保存和跨請求重用HTTP會話。
// static import of SharedHttpSessionConfigurer.sharedHttpSession MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController()) .apply(sharedHttpSession()) .build(); // Use mockMvc to perform requests...
執行請求
您可以執行使用任何HTTP方法的請求。
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
你還可以執行內部使用MockMultipartHttpServletRequest 的文件上傳請求,這樣就不會對多部分請求進行實際解析。
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
可以使用URI模板樣式指定查詢參數
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
還可以添加表示查詢或表單參數的Servlet請求參數
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
在前面的示例中,為每個執行的請求設置contextPath和servletPath會很麻煩。相反,您可以設置默認請求屬性
public class MyWebTests { private MockMvc mockMvc; @Before public void setup() { mockMvc = standaloneSetup(new AccountController()) .defaultRequest(get("/") .contextPath("/app").servletPath("/main") .accept(MediaType.APPLICATION_JSON)).build(); }
}
定義期望值
可以通過在執行請求后附加一個或多個.andExpect(..)調用來定義預期
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
MockMvcResultMatchers.*提供了許多期望值,其中一些還嵌套了更詳細的期望值。
以下測試斷言綁定或驗證失敗:
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
很多時候,在編寫測試時,轉儲已執行請求的結果是有用的。可以執行以下操作,其中print()是MockMVCresResultHandlers的靜態導入:
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
只要請求處理不會導致未處理的異常,print()方法就會將所有可用的結果數據打印到系統輸出. springframework4.2引入了log()方法和print()方法的兩個附加變體,一個接受OutputStream,另一個接受Writer。例如,調用print(系統錯誤)將結果數據打印到系統錯誤調用print(myWriter)時,將結果數據打印到自定義編寫器。如果希望記錄結果數據而不是打印結果數據,可以調用log()方法,該方法將結果數據作為單個調試消息記錄在org.springframework.test.web.servlet.result日志記錄類別。
在某些情況下,您可能希望直接訪問結果並驗證無法通過其他方式驗證的內容。這可以通過在所有其他期望值后面附加.andReturn()來實現。
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
如果所有測試都重復相同的期望值,則可以在構建MockMvc實例時設置一次公共期望值
standaloneSetup(new SimpleController()) .alwaysExpect(status().isOk()) .alwaysExpect(content().contentType("application/json;charset=UTF-8")) .build()。
注意,公共期望總是被應用的,並且在不創建單獨的MockMvc實例的情況下不能被覆蓋
當JSON響應內容包含使用Spring HATEOAS創建的超媒體鏈接時,可以使用JsonPath表達式來驗證生成的鏈接
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
當XML響應內容包含使用Spring HATEOAS創建的超媒體鏈接時,可以使用XPath表達式驗證生成的鏈接:
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
異步請求
springmvc支持的servlet3.0異步請求的工作方式是退出Servlet容器線程,並允許應用程序異步計算響應,然后進行異步調度以完成對Servlet容器線程的處理。
在springmvc測試中,可以通過首先斷言生成的異步值,然后手動執行異步調度,最后驗證響應來測試異步請求。
@Test public void test() throws Exception { MvcResult mvcResult = this.mockMvc.perform(get("/path")) .andExpect(status().isOk()) .andExpect(request().asyncStarted()) .andExpect(request().asyncResult("body")) .andReturn(); this.mockMvc.perform(asyncDispatch(mvcResult)) .andExpect(status().isOk()) .andExpect(content().string("body")); }
流式響應
pringmvc測試中沒有內置用於流式響應無容器測試的選項。使用springmvc流選項的應用程序可以使用WebTestClient對正在運行的服務器執行端到端的集成測試。你也可以在webboot服務器上運行testspring測試。
過濾器注冊
設置MockMvc實例時,可以注冊一個或多個Servlet過濾器實例。
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
注冊的過濾器通過springtest的MockFilterChain調用,最后一個過濾器委托給DispatcherServlet。
Spring MVC測試vs端到端測試
springmvc測試是基於Spring測試模塊的Servlet API模擬實現構建的,不依賴於運行的容器。因此,與運行實際客戶機和實時服務器的完整端到端集成測試相比,存在一些差異。
HtmlUnit集成
Spring提供MockMvc和HtmlUnit之間的集成。這簡化了在使用基於HTML的視圖時執行端到端測試。
- 使用HtmlUnit、WebDriver和Geb等工具輕松測試HTML頁面,無需部署到Servlet容器。
- 在頁面中測試JavaScript。
注意:MockMvc使用不依賴於Servlet容器的模板技術(例如,Thymeleaf、FreeMarker等),但它不適用於jsp,因為它們依賴於Servlet容器
客戶端REST測試
可以使用客戶端測試來測試內部使用RestTemplate的代碼。
RestTemplate restTemplate = new RestTemplate(); MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); mockServer.expect(requestTo("/greeting")).andRespond(withSuccess()); // Test code that uses the above RestTemplate ... mockServer.verify();
在本例中,我們期望有一個對/greeting的請求,並希望返回一個包含text/plain內容的200個響應。我們可以根據需要定義額外的預期請求和存根響應。當我們定義預期的請求和存根響應時,restemplate可以像往常一樣在客戶端代碼中使用。在測試結束時,mockServer.verify()可用於驗證是否滿足了所有期望。
默認情況下,請求按聲明期望的順序進行。您可以在構建服務器時設置ignoreExpectOrder選項,在這種情況下,將檢查所有期望(按順序)以查找與給定請求匹配的內容。這意味着請求可以按任何順序來。
server = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
即使是默認的無序請求,每個請求也只允許執行一次。expect方法提供了一個重載變量,它接受ExpectedCount參數,該參數指定計數范圍(例如,once、manyTimes、max、min、between等)。
RestTemplate restTemplate = new RestTemplate(); MockRestServiceServer mockServer = MockRestServiceServer.bindTo(restTemplate).build(); mockServer.expect(times(2), requestTo("/something")).andRespond(withSuccess()); mockServer.expect(times(3), requestTo("/somewhere")).andRespond(withSuccess()); // ... mockServer.verify();
請注意,如果未設置ignoreExpectOrder(默認值),請求是按聲明的順序進行的,那么該順序只適用於任何預期請求中的第一個。例如,如果“/something”預期出現兩次,后跟“/somewhere”三次,那么在向“/somewhere”發出請求之前,應該有一個對“/something”的請求,但是除了隨后的“/something”和“/somewhere”之外,請求可以隨時出現。
作為以上所有內容的替代,客戶端測試支持還提供了clientHttpPrequestFactory實現,您可以將其配置到restemplate中,以將其綁定到MockMvc實例。它允許使用實際的服務器端邏輯處理請求,但不運行服務器。下面的示例演示如何執行此操作:
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); this.restTemplate = new RestTemplate(new MockMvcClientHttpRequestFactory(mockMvc)); // Test code that uses the above RestTemplate ...
