diff --git a/spring-boot-actuator-docs/pom.xml b/spring-boot-actuator-docs/pom.xml index 2fdd0c8c13..6ef2498e11 100644 --- a/spring-boot-actuator-docs/pom.xml +++ b/spring-boot-actuator-docs/pom.xml @@ -29,6 +29,11 @@ spring-boot-test provided + + org.springframework.boot + spring-boot-test-autoconfigure + provided + ch.qos.logback logback-classic diff --git a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/EndpointDocumentation.java b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/EndpointDocumentation.java index cb120a772f..a1e8005287 100644 --- a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/EndpointDocumentation.java +++ b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/EndpointDocumentation.java @@ -26,23 +26,19 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import javax.servlet.Filter; - import groovy.text.Template; import groovy.text.TemplateEngine; -import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoints; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringApplicationConfiguration; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.restdocs.JUnitRestDocumentation; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -50,12 +46,9 @@ import org.springframework.test.context.web.WebAppConfiguration; 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.setup.MockMvcBuilders; import org.springframework.util.StringUtils; -import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -65,40 +58,21 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", "endpoints.health.sensitive=true", "endpoints.actuator.enabled=false" }) @DirtiesContext +@AutoConfigureRestDocs(EndpointDocumentation.RESTDOCS_OUTPUT_DIR) +@AutoConfigureMockMvc public class EndpointDocumentation { - private static final String RESTDOCS_OUTPUT_DIR = "target/generated-snippets"; - - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( - RESTDOCS_OUTPUT_DIR); - - @Autowired - private WebApplicationContext context; + static final String RESTDOCS_OUTPUT_DIR = "target/generated-snippets"; @Autowired private MvcEndpoints mvcEndpoints; - @Autowired - @Qualifier("metricFilter") - private Filter metricFilter; - - @Autowired - @Qualifier("webRequestLoggingFilter") - private Filter traceFilter; - @Autowired private TemplateEngine templates; + @Autowired private MockMvc mockMvc; - @Before - public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .addFilters(this.metricFilter, this.traceFilter) - .apply(documentationConfiguration(this.restDocumentation)).build(); - } - @Test public void logfile() throws Exception { this.mockMvc.perform(get("/logfile").accept(MediaType.TEXT_PLAIN)) diff --git a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HealthEndpointDocumentation.java b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HealthEndpointDocumentation.java index 229b3fdb4f..be61c5934a 100644 --- a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HealthEndpointDocumentation.java +++ b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HealthEndpointDocumentation.java @@ -16,25 +16,21 @@ package org.springframework.boot.actuate.hypermedia; -import org.junit.Before; -import org.junit.Rule; 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.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringApplicationConfiguration; import org.springframework.http.MediaType; -import org.springframework.restdocs.JUnitRestDocumentation; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -44,23 +40,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @TestPropertySource(properties = { "spring.jackson.serialization.indent_output=true", "endpoints.health.sensitive=false" }) @DirtiesContext +@AutoConfigureMockMvc +@AutoConfigureRestDocs("target/generated-snippets") public class HealthEndpointDocumentation { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( - "target/generated-snippets"); - @Autowired - private WebApplicationContext context; - private MockMvc mockMvc; - @Before - public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)).build(); - } - @Test public void health() throws Exception { this.mockMvc.perform(get("/health").accept(MediaType.APPLICATION_JSON)) diff --git a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java index c462ca281b..2ad906ae26 100644 --- a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java +++ b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/HypermediaEndpointDocumentation.java @@ -16,25 +16,21 @@ package org.springframework.boot.actuate.hypermedia; -import org.junit.Before; -import org.junit.Rule; 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.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringApplicationConfiguration; import org.springframework.http.MediaType; -import org.springframework.restdocs.JUnitRestDocumentation; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -43,23 +39,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebAppConfiguration @TestPropertySource(properties = "spring.jackson.serialization.indent_output=true") @DirtiesContext +@AutoConfigureMockMvc +@AutoConfigureRestDocs("target/generated-snippets") public class HypermediaEndpointDocumentation { - @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( - "target/generated-snippets"); - @Autowired - private WebApplicationContext context; - private MockMvc mockMvc; - @Before - public void setUp() { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation)).build(); - } - @Test public void beans() throws Exception { this.mockMvc.perform(get("/beans").accept(MediaType.APPLICATION_JSON)) diff --git a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/SpringBootHypermediaApplication.java b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/SpringBootHypermediaApplication.java index f41bae3d71..330729d6cb 100644 --- a/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/SpringBootHypermediaApplication.java +++ b/spring-boot-actuator-docs/src/restdoc/java/org/springframework/boot/actuate/hypermedia/SpringBootHypermediaApplication.java @@ -21,9 +21,14 @@ import groovy.text.TemplateEngine; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +// Flyway must go first @SpringBootApplication +@Import({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) public class SpringBootHypermediaApplication { @Bean diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 1f83e68241..21dee932c9 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -4744,6 +4744,84 @@ database you can use the `@AutoConfigureTestDatabase` annotation: +[[boot-features-testing-spring-boot-applications-testing-autoconfigurd-rest-docs]] +==== Auto-configured Spring REST Docs tests +Test `@AutoConfigureRestDocs` annotation can be used if you want to use Spring REST Docs +in your tests. It will automatically configure `MockMvc` to use Spring REST Docs and +removes the need for Spring REST Docs' JUnit rule. + +[source,java,indent=0] +---- + 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("target/generated-snippets") + 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")); + } + + } +---- + +In addition to configuring the output directory, `@AutoConfigureRestDocs` can also +configure the host, scheme, and port that will appear in any documented URIs. If you +require more control over Spring REST Docs' configuration a +`RestDocsMockMvcConfigurationCustomizer` bean can be used: + +[source,java,indent=0] +---- + @TestConfiguration + static class CustomizationConfiguration + implements RestDocsMockMvcConfigurationCustomizer { + + @Override + public void customize(MockMvcRestDocumentationConfigurer configurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); + } + + } +---- + +If you want to make use of Spring REST Docs' support for a parameterized output directory, +you can create a `RestDocumentationResultHandler` bean. The auto-configuration will +call `alwaysDo` with this result handler, thereby causing each `MockMvc` call to +automatically generate the default snippets: + +[source,java,indent=0] +---- + @TestConfiguration + static class ResultHandlerConfiguration{ + + @Bean + public RestDocumentationResultHandler restDocumentation() { + return MockMvcRestDocumentation.document("{method-name}"); + } + + } +---- + + + [[boot-features-testing-spring-boot-applications-with-spock]] ==== Using Spock to test Spring Boot applications If you wish to use Spock to test a Spring Boot application you should add a dependency diff --git a/spring-boot-parent/src/checkstyle/checkstyle.xml b/spring-boot-parent/src/checkstyle/checkstyle.xml index 61889c0536..32cc2a1bf1 100644 --- a/spring-boot-parent/src/checkstyle/checkstyle.xml +++ b/spring-boot-parent/src/checkstyle/checkstyle.xml @@ -71,7 +71,7 @@ + value="org.assertj.core.api.Assertions.*, org.junit.Assert.*, org.junit.Assume.*, org.junit.internal.matchers.ThrowableMessageMatcher.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.springframework.boot.configurationprocessor.ConfigurationMetadataMatchers.*, org.springframework.boot.configurationprocessor.TestCompiler.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*, org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*, org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*, org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo" /> diff --git a/spring-boot-test-autoconfigure/pom.xml b/spring-boot-test-autoconfigure/pom.xml index c62b694785..c6a2edf7fb 100644 --- a/spring-boot-test-autoconfigure/pom.xml +++ b/spring-boot-test-autoconfigure/pom.xml @@ -96,20 +96,30 @@ true + + org.springframework.restdocs + spring-restdocs-mockmvc + true + - org.aspectj - aspectjrt + ch.qos.logback + logback-classic + test + + + com.h2database + h2 test org.aspectj - aspectjweaver + aspectjrt test - com.h2database - h2 + org.aspectj + aspectjweaver test @@ -123,8 +133,8 @@ test - ch.qos.logback - logback-classic + org.springframework.hateoas + spring-hateoas test diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/AutoConfigureRestDocs.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/AutoConfigureRestDocs.java new file mode 100644 index 0000000000..b2cbbb859b --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/AutoConfigureRestDocs.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.properties.PropertyMapping; +import org.springframework.context.annotation.Import; + +/** + * Annotation that can be applied to a test class to enable and configure + * auto-configuration of Spring REST Docs. Allows configuration of the output directory + * and the host, scheme, and port of generated URIs. When further configuration is + * required a {@link RestDocsMockMvcConfigurationCustomizer} bean can be used. + * + * @author Andy Wilkinson + * @since 1.4.0 + * @see RestDocsAutoConfiguration + * @see RestDocsMockMvcConfigurationCustomizer + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ImportAutoConfiguration(RestDocsAutoConfiguration.class) +@Import(RestDocumentationContextProviderRegistrar.class) +@PropertyMapping("spring.test.restdocs") +public @interface AutoConfigureRestDocs { + + /** + * The output directory to which generated snippets will be written. A synonym for + * {@link #outputDir}. + * @return the output directory + */ + String value() default ""; + + /** + * The output directory to which generated snippets will be written. A synonym for + * {@link #value}. + * @return the output directory + */ + String outputDir() default ""; + + /** + * The scheme (typically {@code http} or {@code https}) to be used in documented URIs. + * Defaults to {@code http}. + * @return the scheme + */ + String uriScheme() default "http"; + + /** + * The host to be used in documented URIs. Defaults to {@code localhost}. + * @return the host + */ + String uriHost() default "localhost"; + + /** + * The port to be used in documented URIs. Defaults to {@code 8080}. + * @return the port + */ + int uriPort() default 8080; + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java new file mode 100644 index 0000000000..b1536c7336 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Spring REST Docs. + * + * @author Andy Wilkinson + */ +@Configuration +@ConditionalOnWebApplication +@EnableConfigurationProperties +class RestDocsAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(MockMvcRestDocumentationConfigurer.class) + public MockMvcRestDocumentationConfigurer restDocsMockMvcConfigurer( + ObjectProvider configurationCustomizerProvider, + RestDocumentationContextProvider contextProvider) { + MockMvcRestDocumentationConfigurer configurer = MockMvcRestDocumentation + .documentationConfiguration(contextProvider); + RestDocsMockMvcConfigurationCustomizer configurationCustomizer = configurationCustomizerProvider + .getIfAvailable(); + if (configurationCustomizer != null) { + configurationCustomizer.customize(configurer); + } + return configurer; + } + + @Bean + @ConfigurationProperties("spring.test.restdocs") + public RestDocsMockMvcBuilderCustomizer restDocumentationConfigurer( + MockMvcRestDocumentationConfigurer configurer, + ObjectProvider resultHandler) { + return new RestDocsMockMvcBuilderCustomizer(configurer, + resultHandler.getIfAvailable()); + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcBuilderCustomizer.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcBuilderCustomizer.java new file mode 100644 index 0000000000..d0b2b91c7f --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcBuilderCustomizer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.test.autoconfigure.web.servlet.MockMvcBuilderCustomizer; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; +import org.springframework.util.StringUtils; + +/** + * A {@link MockMvcBuilderCustomizer} that configures Spring REST Docs. + * + * @author Andy Wilkinson + */ +class RestDocsMockMvcBuilderCustomizer + implements InitializingBean, MockMvcBuilderCustomizer { + + private final MockMvcRestDocumentationConfigurer delegate; + + private final RestDocumentationResultHandler resultHandler; + + private String uriScheme; + + private String uriHost; + + private Integer uriPort; + + RestDocsMockMvcBuilderCustomizer(MockMvcRestDocumentationConfigurer delegate, + RestDocumentationResultHandler resultHandler) { + this.delegate = delegate; + this.resultHandler = resultHandler; + } + + public String getUriScheme() { + return this.uriScheme; + } + + public void setUriScheme(String uriScheme) { + this.uriScheme = uriScheme; + } + + public String getUriHost() { + return this.uriHost; + } + + public void setUriHost(String uriHost) { + this.uriHost = uriHost; + } + + public Integer getUriPort() { + return this.uriPort; + } + + public void setUriPort(Integer uriPort) { + this.uriPort = uriPort; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (StringUtils.hasText(this.uriScheme)) { + this.delegate.uris().withScheme(this.uriScheme); + } + if (StringUtils.hasText(this.uriHost)) { + this.delegate.uris().withHost(this.uriHost); + } + if (this.uriPort != null) { + this.delegate.uris().withPort(this.uriPort); + } + } + + @Override + public void customize(ConfigurableMockMvcBuilder builder) { + builder.apply(this.delegate); + if (this.resultHandler != null) { + builder.alwaysDo(this.resultHandler); + } + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcConfigurationCustomizer.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcConfigurationCustomizer.java new file mode 100644 index 0000000000..928e160b34 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsMockMvcConfigurationCustomizer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; + +/** + * A customizer for {@link MockMvcRestDocumentationConfigurer}. If a + * {@code RestDocsMockMvcConfigurationCustomizer} bean is found in the application context + * it will be {@link #customize called} to customize the + * {@code MockMvcRestDocumentationConfigurer} before it is applied. Intended for use only + * when the attributes on {@link AutoConfigureRestDocs} do not provide sufficient + * customization. + * + * @author Andy Wilkinson + * @since 1.4.0 + */ +public interface RestDocsMockMvcConfigurationCustomizer { + + /** + * Customize the given {@configurer}. + * @param configurer the configurer + */ + void customize(MockMvcRestDocumentationConfigurer configurer); + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestExecutionListener.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestExecutionListener.java new file mode 100644 index 0000000000..0c8fb183c5 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestExecutionListener.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.restdocs.ManualRestDocumentation; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.util.ClassUtils; + +/** + * A {@link TestExecutionListener} for Spring REST Docs that removes the need for a + * @Rule when using JUnit or manual before and after test calls when using + * TestNG. + * + * @author Andy Wilkinson + * @since 1.4.0 + */ +public class RestDocsTestExecutionListener extends AbstractTestExecutionListener { + + private static final String REST_DOCS_CLASS = "org.springframework.restdocs.ManualRestDocumentation"; + + @Override + public void beforeTestMethod(TestContext testContext) throws Exception { + if (restDocsIsPresent()) { + new DocumentationHandler().beforeTestMethod(testContext); + } + } + + @Override + public void afterTestMethod(TestContext testContext) throws Exception { + if (restDocsIsPresent()) { + new DocumentationHandler().afterTestMethod(testContext); + } + } + + private boolean restDocsIsPresent() { + return ClassUtils.isPresent(REST_DOCS_CLASS, getClass().getClassLoader()); + } + + private static class DocumentationHandler { + + private void beforeTestMethod(TestContext testContext) throws Exception { + ManualRestDocumentation restDocumentation = findManualRestDocumentation( + testContext); + if (restDocumentation != null) { + restDocumentation.beforeTest(testContext.getTestClass(), + testContext.getTestMethod().getName()); + } + } + + private void afterTestMethod(TestContext testContext) { + ManualRestDocumentation restDocumentation = findManualRestDocumentation( + testContext); + if (restDocumentation != null) { + restDocumentation.afterTest(); + } + } + + private ManualRestDocumentation findManualRestDocumentation( + TestContext testContext) { + try { + return testContext.getApplicationContext() + .getBean(ManualRestDocumentation.class); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java new file mode 100644 index 0000000000..0963da774a --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocumentationContextProviderRegistrar.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.util.Map; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.restdocs.ManualRestDocumentation; +import org.springframework.util.StringUtils; + +/** + * {@link ImportBeanDefinitionRegistrar} used by {@link AutoConfigureRestDocs}. + * + * @author Andy Wilkinson + */ +class RestDocumentationContextProviderRegistrar implements ImportBeanDefinitionRegistrar { + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder + .genericBeanDefinition(ManualRestDocumentation.class) + .addConstructorArgValue(determineOutputDir(importingClassMetadata)) + .getBeanDefinition(); + registry.registerBeanDefinition(ManualRestDocumentation.class.getName(), + beanDefinition); + } + + private String determineOutputDir(AnnotationMetadata annotationMetadata) { + Map annotationAttributes = annotationMetadata + .getAnnotationAttributes(AutoConfigureRestDocs.class.getName()); + String outputDir = (String) annotationAttributes.get("outputDir"); + if (!StringUtils.hasText(outputDir)) { + outputDir = (String) annotationAttributes.get("value"); + if (!StringUtils.hasText(outputDir)) { + throw new IllegalStateException( + "Either value or outputDir must be specified on @AutoConfigureRestDocs"); + } + } + return outputDir; + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index 136c97df4e..03cd4eaee3 100644 --- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -7,4 +7,5 @@ org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCus # Test Execution Listeners org.springframework.test.context.TestExecutionListener=\ org.springframework.boot.test.autoconfigure.AutoConfigureReportTestExecutionListener,\ -org.springframework.boot.test.autoconfigure.json.JsonTesterInitializationTestExecutionListener +org.springframework.boot.test.autoconfigure.json.JsonTesterInitializationTestExecutionListener,\ +org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/ContentContainingCondition.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/ContentContainingCondition.java new file mode 100644 index 0000000000..551b710fa5 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/ContentContainingCondition.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; + +import org.assertj.core.api.Condition; +import org.assertj.core.description.TextDescription; + +import org.springframework.util.FileCopyUtils; + +/** + * A {@link Condition} to assert that a file's contents contain a given string. + * + * @author Andy Wilkinson + */ +class ContentContainingCondition extends Condition { + + private final String toContain; + + ContentContainingCondition(String toContain) { + super(new TextDescription("content containing %s", toContain)); + this.toContain = toContain; + } + + @Override + public boolean matches(File value) { + Reader reader = null; + try { + reader = new FileReader(value); + String content = FileCopyUtils.copyToString(new FileReader(value)); + System.out.println(content); + return content.contains(this.toContain); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + finally { + if (reader != null) { + try { + reader.close(); + } + catch (IOException ex) { + // Ignore + } + } + } + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java new file mode 100644 index 0000000000..ac32427b64 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationAdvancedConfigurationIntegrationTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.io.File; + +import org.assertj.core.api.Condition; +import org.junit.Before; +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.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.templates.TemplateFormats; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.FileSystemUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; +import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +/** + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@WebMvcTest(RestDocsTestController.class) +@AutoConfigureRestDocs(outputDir = "target/generated-snippets") +public class RestDocsAutoConfigurationAdvancedConfigurationIntegrationTests { + + @Before + public void deleteSnippets() { + FileSystemUtils.deleteRecursively(new File("target/generated-snippets")); + } + + @Autowired + private MockMvc mvc; + + @Autowired + private RestDocumentationResultHandler document; + + @Test + public void snippetGeneration() throws Exception { + this.document.snippets(links( + linkWithRel("self").description("Canonical location of this resource"))); + this.mvc.perform(get("/")); + File defaultSnippetsDir = new File( + "target/generated-snippets/snippet-generation"); + assertThat(defaultSnippetsDir).exists(); + assertThat(new File(defaultSnippetsDir, "curl-request.md")) + .has(contentContaining("'http://localhost:8080/'")); + assertThat(new File(defaultSnippetsDir, "links.md")).isFile(); + } + + private Condition contentContaining(String toContain) { + return new ContentContainingCondition(toContain); + } + + @TestConfiguration + public static class CustomizationConfiguration + implements RestDocsMockMvcConfigurationCustomizer { + + @Bean + public RestDocumentationResultHandler restDocumentation() { + return MockMvcRestDocumentation.document("{method-name}"); + } + + @Override + public void customize(MockMvcRestDocumentationConfigurer configurer) { + configurer.snippets().withTemplateFormat(TemplateFormats.markdown()); + } + + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationIntegrationTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationIntegrationTests.java new file mode 100644 index 0000000000..e349425609 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsAutoConfigurationIntegrationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.io.File; + +import org.assertj.core.api.Condition; +import org.junit.Before; +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.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.FileSystemUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +/** + * Tests for {@link RestDocsAutoConfiguration}. + * + * @author Andy Wilkinson + */ +@RunWith(SpringRunner.class) +@WebMvcTest +@AutoConfigureRestDocs(outputDir = "target/generated-snippets", uriScheme = "https", uriHost = "api.example.com", uriPort = 443) +public class RestDocsAutoConfigurationIntegrationTests { + + @Before + public void deleteSnippets() { + FileSystemUtils.deleteRecursively(new File("target/generated-snippets")); + } + + @Autowired + private MockMvc mvc; + + @Test + public void defaultSnippetsAreWritten() throws Exception { + this.mvc.perform(get("/")).andDo(document("default-snippets")); + File defaultSnippetsDir = new File("target/generated-snippets/default-snippets"); + assertThat(defaultSnippetsDir).exists(); + assertThat(new File(defaultSnippetsDir, "curl-request.adoc")) + .has(contentContaining("'https://api.example.com/'")); + assertThat(new File(defaultSnippetsDir, "http-request.adoc")) + .has(contentContaining("api.example.com")); + assertThat(new File(defaultSnippetsDir, "http-response.adoc")).isFile(); + } + + private Condition contentContaining(String toContain) { + return new ContentContainingCondition(toContain); + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestApplication.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestApplication.java new file mode 100644 index 0000000000..eef3c6095f --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Test application used with {@link AutoConfigureRestDocs} tests. + * + * @author Andy Wilkinson + */ +@SpringBootApplication +public class RestDocsTestApplication { + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestController.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestController.java new file mode 100644 index 0000000000..3fa6120c39 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/restdocs/RestDocsTestController.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.autoconfigure.restdocs; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.mvc.ControllerLinkBuilder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RestDocsTestController { + + @ResponseBody + @RequestMapping(path = "/", produces = MediaTypes.HAL_JSON_VALUE) + public Map index() { + Map response = new HashMap(); + Map links = new HashMap(); + links.put("self", ControllerLinkBuilder.linkTo(getClass()).toUri().toString()); + response.put("_links", links); + return response; + } + +}