在测试应用程序时,Spring Boot 提供了许多使用工具和注解。测试支持由两个模块提供:
大多数开发人员使用
在编写测试时,我们通常发现这些公共库是有用的。如果这些库不适合你的需求,你可以添加自己的其它测试依赖项。 依赖注入的一个主要优点是它应该使你的代码更容易进行单元测试。可以使用新运算符实例化对象,甚至不涉及 Spring。还可以使用模拟对象替代实际依赖项。
通常,你需要越过单元测试并开始集成测试(使用 Spring
Spring Framework 包括用于这种集成测试的专用测试模块。你可以直接向
如果以前没有使用
Spring Boot 应用程序是一个 Spring
Spring Boot 提供了一个
可以使用
如果 Spring MVC 可用,则配置有规则的基于 MVC 的应用程序上下文。如果你只有 Spring WebFlux,我们将检测并配置基于 WebFlux 的应用程序上下文。
如果两者都存在,则优先使用 Spring MVC。如果要在这种情况下测试一个响应式 Web 应用程序,则必须设置 @RunWith(SpringRunner.class) @SpringBootTest(properties = "spring.main.web-application-type=reactive") public class MyWebFluxTests { ... }
如果你熟悉 Spring 测试框架,可以使用
在测试 Spring Boot 应用程序时,通常不需要这样做。Spring Boot 的
搜索算法从包含测试的包中起作用,直到找到一个用
如果你要自定义主配置。可以使用嵌套的
如果你的应用程序使用组件扫描(例如,如果你使用
正如我们之前看到的,可以在测试的内部类上使用 @RunWith(SpringRunner.class) @SpringBootTest @Import(MyTestsConfiguration.class) public class MyTests { @Test public void exampleTest() { ... } }
如果需要启动全运行服务器,我们建议你使用随机端口。如果使用
可以使用 import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class RandomPortWebTestClientExampleTests { @Autowired private WebTestClient webClient; @Test public void exampleTest() { this.webClient.get().uri("/").exchange().expectStatus().isOk() .expectBody(String.class).isEqualTo("Hello World"); } }
Spring Boot 还提供了一个 import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class RandomPortTestRestTemplateExampleTests { @Autowired private TestRestTemplate restTemplate; @Test public void exampleTest() { String body = this.restTemplate.getForObject("/", String.class); assertThat(body).isEqualTo("Hello World"); } }
当测试上下文框架缓存上下文时,默认情况下禁用 JMX,以防止相同组件在同一域上注册。如果这样的测试需要访问一个 @RunWith(SpringRunner.class) @SpringBootTest(properties = "spring.jmx.enabled=true") @DirtiesContext public class SampleJmxTests { @Autowired private MBeanServer mBeanServer; @Test public void exampleTest() { // ... } } 在运行测试时,有时需要在应用程序上下文中模拟某些组件。例如,你可能在开发过程中面对一些不可用的远程服务。当你想模拟在真实环境中可能难以触发的故障时,模拟也是有用的。
Spring Boot 包含一个
下面的示例用模拟实现替换现有的 import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.context.*; import org.springframework.boot.test.mock.mockito.*; import org.springframework.test.context.junit4.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; @RunWith(SpringRunner.class) @SpringBootTest public class MyTests { @MockBean private RemoteService remoteService; @Autowired private Reverser reverser; @Test public void exampleTest() { // RemoteService has been injected into the reverser bean given(this.remoteService.someCall()).willReturn("mock"); String reverse = reverser.reverseSomeCall(); assertThat(reverse).isEqualTo("kcom"); } }
另外,你可以使用
Spring Boot 的自动配置系统在应用程序中运行良好,但有时对于测试来说可能有点太多。仅加载测试应用程序的切片所需要的配置的部分通常是有帮助的。例如,你可能希望测试 Spring MVC 控制器正确地映射 URL,并且不希望在这些测试中涉及数据库调用,或者你可能想要测试 JPA 实体,并且当这些测试运行时,你对 web 层不感兴趣。
若要测试 JSON 对象序列化和反序列化正在按预期的方式工作,可以使用
如果需要配置自动配置的元素,可以使用
Spring Boot 包含基于 AssertJ 的助手,这些助手与 JSONassert 和 JsonPath 库一起工作,以检查 JSON 是否按预期的方式出现。 import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.json.*; import org.springframework.boot.test.context.*; import org.springframework.boot.test.json.*; import org.springframework.test.context.junit4.*; import static org.assertj.core.api.Assertions.*; @RunWith(SpringRunner.class) @JsonTest public class MyJsonTests { @Autowired private JacksonTester<VehicleDetails> json; @Test public void testSerialize() throws Exception { VehicleDetails details = new VehicleDetails("Honda", "Civic"); // Assert against a `.json` file in the same package as the test assertThat(this.json.write(details)).isEqualToJson("expected.json"); // Or use JSON path based assertions assertThat(this.json.write(details)).hasJsonPathStringValue("@.make"); assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make") .isEqualTo("Honda"); } @Test public void testDeserialize() throws Exception { String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"; assertThat(this.json.parse(content)) .isEqualTo(new VehicleDetails("Ford", "Focus")); assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford"); } }
附录中可以找到由
为了测试 Spring MVC 控制器是否正常工作,请使用
通常,
import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.web.servlet.*; import org.springframework.boot.test.mock.mockito.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @WebMvcTest(UserVehicleController.class) public class MyControllerTests { @Autowired private MockMvc mvc; @MockBean private UserVehicleService userVehicleService; @Test public void testExample() throws Exception { given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()).andExpect(content().string("Honda Civic")); } }
如果使用 HtmlUnit 或 Selenium,自动配置还提供 HTMLUnit import com.gargoylesoftware.htmlunit.*; import org.junit.*; import org.junit.runner.*; import org.springframework.beans.factory.annotation.*; import org.springframework.boot.test.autoconfigure.web.servlet.*; import org.springframework.boot.test.mock.mockito.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.BDDMockito.*; @RunWith(SpringRunner.class) @WebMvcTest(UserVehicleController.class) public class MyHtmlUnitTests { @Autowired private WebClient webClient; @MockBean private UserVehicleService userVehicleService; @Test public void testExample() throws Exception { given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); HtmlPage page = this.webClient.getPage("/sboot/vehicle.html"); assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic"); } }
附录中可以找到由
为了测试 Spring WebFlux 控制器是否正常工作,你可以使用
通常,
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; @RunWith(SpringRunner.class) @WebFluxTest(UserVehicleController.class) public class MyControllerTests { @Autowired private WebTestClient webClient; @MockBean private UserVehicleService userVehicleService; @Test public void testExample() throws Exception { given(this.userVehicleService.getVehicleDetails("sboot")) .willReturn(new VehicleDetails("Honda", "Civic")); this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN) .exchange() .expectStatus().isOk() .expectBody(String.class).isEqualTo("Honda Civic"); } }
附录中可以找到由
可以使用 默认情况下,数据 JPA 测试是事务性的,并在每次测试结束时回滚。更多详细内容请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试或整个类的事务管理,如下: import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringRunner.class) @DataJpaTest @Transactional(propagation = Propagation.NOT_SUPPORTED) public class ExampleNonTransactionalTests { }
数据 JPA 测试也可以注入 import org.junit.*; import org.junit.runner.*; import org.springframework.boot.test.autoconfigure.orm.jpa.*; import static org.assertj.core.api.Assertions.*; @RunWith(SpringRunner.class) @DataJpaTest public class ExampleRepositoryTests { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository repository; @Test public void testExample() throws Exception { this.entityManager.persist(new User("sboot", "1234")); User user = this.repository.findByUsername("sboot"); assertThat(user.getUsername()).isEqualTo("sboot"); assertThat(user.getVin()).isEqualTo("1234"); } } 在内存中,嵌入式数据库通常对测试工作的很好,因为它们速度快,不需要任何安装。但是,如果你喜欢对真实数据库运行测试,则可以使用 注解,如下面的示例所示: @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace=Replace.NONE) public class ExampleRepositoryTests { // ... }
附录中可以找到由
默认情况下,JDBC 测试都是事务性的,在每次测试结束时都会回滚。更多详细信息请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试的或整个类的事务管理,如下: import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringRunner.class) @JdbcTest @Transactional(propagation = Propagation.NOT_SUPPORTED) public class ExampleNonTransactionalTests { }
如果你希望测试与实际数据库运行,那么可以使用与
附录中可以找到由
你可以以
import org.jooq.DSLContext; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.jooq.JooqTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @JooqTest public class ExampleJooqTests { @Autowired private DSLContext dslContext; } JOOQ 测试是事务性的,在每次测试结束时都会回滚。如果这不是你想要的,可以禁用测试的或整个测试类的事务管理,如 JDBC 示例中所示。
附录中可以找到由
可以使用
下面的类展示了使用中的 import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataMongoTest public class ExampleDataMongoTests { @Autowired private MongoTemplate mongoTemplate; // } 内存中的嵌入式 MongoDB 通常对测试工作很好,因为它很快,并且不需要开发人员进行任何安装。但是,如果你喜欢对一个真正的 MongoDB 服务器进行测试,则应该排除嵌入式 MongoDB 自动配置,如下面的示例所示: import org.junit.runner.RunWith; import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration; import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class) public class ExampleDataMongoNonEmbeddedTests { }
附录中可以找到由
可以使用 下面的示例展示了在 Spring Boot 中使用 Neo4J 测试的典型设置: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataNeo4jTest public class ExampleDataNeo4jTests { @Autowired private YourRepository repository; // } 默认情况下,Neo4j 数据测试是事务性的,并且在每次测试结束时回滚。更多详细信息请参阅 Spring Framework 参考文档中的相关部分。如果这不是你想要的,可以禁用测试的或整个类的事务管理,如下: import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringRunner.class) @DataNeo4jTest @Transactional(propagation = Propagation.NOT_SUPPORTED) public class ExampleNonTransactionalTests { }
附录中可以找到由
可以使用
下面的示例展示了使用的 import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataRedisTest public class ExampleDataRedisTests { @Autowired private YourRepository repository; // }
附录中可以找到由
可以使用
下面的示例展示了使用中的 import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; import org.springframework.ldap.core.LdapTemplate; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataLdapTest public class ExampleDataLdapTests { @Autowired private LdapTemplate ldapTemplate; // } 在内存中的嵌入式 LDAP 通常对测试工作很好,因为它很快,不需要任何开发人员安装。但是,如果你喜欢对真正的 LDAP 服务器进行测试,则应该排除嵌入式 LDAP 自动配置,如下面的示例所示: import org.junit.runner.RunWith; import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration; import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class) public class ExampleDataLdapNonEmbeddedTests { }
附录中可以找到由
可以使用 @RunWith(SpringRunner.class) @RestClientTest(RemoteVehicleDetailsService.class) public class ExampleRestClientTest { @Autowired private RemoteVehicleDetailsService service; @Autowired private MockRestServiceServer server; @Test public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() throws Exception { this.server.expect(requestTo("/greet/details")) .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); String greeting = this.service.callRestService(); assertThat(greeting).isEqualTo("hello"); } }
附录中可以找到由
你可以使用
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @WebMvcTest(UserController.class) @AutoConfigureRestDocs public class UserDocumentationTests { @Autowired private MockMvc mvc; @Test public void listUsers() throws Exception { this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) .andDo(document("list-users")); } }
如果你需要更多对 Spring REST Docs 配置的控制,而不是 @TestConfiguration static class CustomizationConfiguration implements RestDocsMockMvcConfigurationCustomizer { @Override public void customize(MockMvcRestDocumentationConfigurer configurer) { configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); } }
如果你想使用 Spring REST Docs 支持参数化的输出目录,可以创建 @TestConfiguration static class ResultHandlerConfiguration { @Bean public RestDocumentationResultHandler restDocumentation() { return MockMvcRestDocumentation.document("{method-name}"); } }
import io.restassured.specification.RequestSpecification; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.test.context.junit4.SpringRunner; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.is; import static org.springframework.restdocs.restassured3.RestAssuredRestDocumentation.document; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @AutoConfigureRestDocs public class UserDocumentationTests { @LocalServerPort private int port; @Autowired private RequestSpecification documentationSpec; @Test public void listUsers() { given(this.documentationSpec).filter(document("list-users")).when() .port(this.port).get("/").then().assertThat().statusCode(is(200)); } }
如果你需要更多的对 Spring REST Docs 配置的控制,而不是由 @TestConfiguration public static class CustomizationConfiguration implements RestDocsRestAssuredConfigurationCustomizer { @Override public void customize(RestAssuredRestDocumentationConfigurer configurer) { configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); } }
如果以合理的方式构造代码,你的 因此,重要的是不要用特定于其函数式特定区域的配置设置来丢弃应用程序的主类。
假设你正在使用 Spring Batch,并且依赖于它的自动配置。你可以把你的 @SpringBootApplication @EnableBatchProcessing public class SampleApplication { ... }
因为这个类是测试的源配置,所以任何切片测试实际上都试图启动 Spring Batch,这绝对不是你想要做的。推荐的方法是将特定于区域的配置移动到与应用程序相同级别的单独的 @Configuration @EnableBatchProcessing public class BatchConfiguration { ... }
混乱的另一个来源是类路径扫描。假设在以合理的方式构造代码时,需要扫描一个附加的包。你的应用程序可能类似于以下代码: @SpringBootApplication @ComponentScan({ "com.example.app", "org.acme.another" }) public class SampleApplication { ... }
这样做,只扫描指定的这两个包,而不考虑你选择的切面,有效地覆盖了默认的组件扫描指令带来的副作用。例如,
如果你希望使用 Spock 来测试 Spring Boot 应用程序,则应该将 Spock 的
在测试应用程序时通常有用的几个测试实用程序类被打包为
@ContextConfiguration(classes = Config.class, initializers = ConfigFileApplicationContextInitializer.class)
TestPropertyValues.of("org=Spring", "name=Boot").applyTo(env);
import org.junit.Rule; import org.junit.Test; import org.springframework.boot.test.rule.OutputCapture; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class MyTest { @Rule public OutputCapture capture = new OutputCapture(); @Test public void testName() throws Exception { System.out.println("Hello World!"); assertThat(capture.toString(), containsString("World")); } }
public class MyTest { private TestRestTemplate template = new TestRestTemplate(); @Test public void testRequest() throws Exception { HttpHeaders headers = this.template.getForEntity( "http://myhost.example.com/example", String.class).getHeaders(); assertThat(headers.getLocation()).hasHost("other.example.com"); } }
或者,如果你使用 @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class SampleWebClientTests { @Autowired private TestRestTemplate template; @Test public void testRequest() { HttpHeaders headers = this.template.getForEntity("/example", String.class) .getHeaders(); assertThat(headers.getLocation()).hasHost("other.example.com"); } @TestConfiguration static class Config { @Bean public RestTemplateBuilder restTemplateBuilder() { return new RestTemplateBuilder().setConnectTimeout(1000).setReadTimeout(1000); } } } Spring Boot 为嵌入式 Tomcat、Jetty 和 Undertow 提供了 WebSockets 自动配置。如果将 war 文件部署到独立容器,Spring Boot 假定容器负责其 WebSocket 支持的配置。
Spring Framework 提供了丰富的 WebSocket 支持,可以通过
Spring Boot 提供 Web Services 自动配置,因此你必须做的就是定义
Spring Web Services 特性可以很容易地被
可以分别为 WSDL 和 XSD 自动创建 spring.webservices.wsdl-locations=classpath:/wsdl
如果你需要从应用程序调用远程 Web services,可以使用 下面的代码展示了一个典型的例子: @Service public class MyService { private final WebServiceTemplate webServiceTemplate; public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) { this.webServiceTemplate = webServiceTemplateBuilder.build(); } public DetailsResp someWsCall(DetailsReq detailsReq) { return (DetailsResp) this.webServiceTemplate.marshalSendAndReceive(detailsReq, new SoapActionCallback(ACTION)); } }
默认情况下, @Bean public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) { return builder.messageSenders(new HttpWebServiceMessageSenderBuilder() .setConnectTimeout(5000).setReadTimeout(2000).build()).build(); } |