Spring Integration Testing(集成测试)

本节介绍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 ...

 

posted @ 2020-07-14 22:42  codedot  阅读(1758)  评论(0编辑  收藏  举报