diff --git a/spring-boot-samples/spring-boot-sample-web-static/src/test/java/sample/ui/SampleWebStaticApplicationTests.java b/spring-boot-samples/spring-boot-sample-web-static/src/test/java/sample/ui/SampleWebStaticApplicationTests.java index 8105354247..899d799e05 100644 --- a/spring-boot-samples/spring-boot-sample-web-static/src/test/java/sample/ui/SampleWebStaticApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-web-static/src/test/java/sample/ui/SampleWebStaticApplicationTests.java @@ -16,11 +16,15 @@ package sample.ui; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.TestRestTemplate; import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -28,9 +32,6 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - /** * Basic integration tests for demo application. * @@ -39,14 +40,17 @@ import static org.junit.Assert.assertTrue; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleWebStaticApplication.class) @WebAppConfiguration -@IntegrationTest +@IntegrationTest("server.port=0") @DirtiesContext public class SampleWebStaticApplicationTests { + + @Value("${local.server.port}") + private int port = 0; @Test public void testHome() throws Exception { ResponseEntity entity = new TestRestTemplate().getForEntity( - "http://localhost:8080", String.class); + "http://localhost:" + port, String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity .getBody().contains("Static")); @@ -55,7 +59,7 @@ public class SampleWebStaticApplicationTests { @Test public void testCss() throws Exception { ResponseEntity<String> entity = new TestRestTemplate().getForEntity( - "http://localhost:8080/webjars/bootstrap/3.0.3/css/bootstrap.min.css", + "http://localhost:" + port + "/webjars/bootstrap/3.0.3/css/bootstrap.min.css", String.class); assertEquals(HttpStatus.OK, entity.getStatusCode()); assertTrue("Wrong body:\n" + entity.getBody(), entity.getBody().contains("body")); diff --git a/spring-boot/src/main/java/org/springframework/boot/test/EmbeddedServletContainerListener.java b/spring-boot/src/main/java/org/springframework/boot/test/EmbeddedServletContainerListener.java new file mode 100644 index 0000000000..f2cce73ce3 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/test/EmbeddedServletContainerListener.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2013 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; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.AbstractTestExecutionListener; + +/** + * Listener that injects the server port (if one is discoverable from the application + * context)into a field annotated with {@link Value @Value("dynamic.port")}. + * + * @author Dave Syer + */ +public class EmbeddedServletContainerListener extends AbstractTestExecutionListener { + + @Override + public void prepareTestInstance(TestContext testContext) throws Exception { + ApplicationContext context = testContext.getApplicationContext(); + if (!(context instanceof EmbeddedWebApplicationContext)) { + return; + } + EmbeddedWebApplicationContext embedded = (EmbeddedWebApplicationContext) context; + final int port = embedded.getEmbeddedServletContainer().getPort(); + EnvironmentTestUtils.addEnvironment(embedded, "local.server.port:" + port); + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/test/EnvironmentTestUtils.java b/spring-boot/src/main/java/org/springframework/boot/test/EnvironmentTestUtils.java index e32ced505d..b024b208dc 100644 --- a/spring-boot/src/main/java/org/springframework/boot/test/EnvironmentTestUtils.java +++ b/spring-boot/src/main/java/org/springframework/boot/test/EnvironmentTestUtils.java @@ -55,16 +55,29 @@ public abstract class EnvironmentTestUtils { */ public static void addEnvironment(ConfigurableEnvironment environment, String... pairs) { + addEnvironment("test", environment, pairs); + } + + /** + * Add additional (high priority) values to an {@link Environment}. Name-value pairs + * can be specified with colon (":") or equals ("=") separators. + * + * @param environment the environment to modify + * @param name the property source name + * @param pairs the name:value pairs + */ + public static void addEnvironment(String name, ConfigurableEnvironment environment, + String... pairs) { MutablePropertySources sources = environment.getPropertySources(); Map<String, Object> map; - if (!sources.contains("test")) { + if (!sources.contains(name)) { map = new HashMap<String, Object>(); - MapPropertySource source = new MapPropertySource("test", map); + MapPropertySource source = new MapPropertySource(name, map); sources.addFirst(source); } else { @SuppressWarnings("unchecked") - Map<String, Object> value = (Map<String, Object>) sources.get("test") + Map<String, Object> value = (Map<String, Object>) sources.get(name) .getSource(); map = value; } diff --git a/spring-boot/src/main/java/org/springframework/boot/test/IntegrationTest.java b/spring-boot/src/main/java/org/springframework/boot/test/IntegrationTest.java index 9eeea3c552..9417047c26 100644 --- a/spring-boot/src/main/java/org/springframework/boot/test/IntegrationTest.java +++ b/spring-boot/src/main/java/org/springframework/boot/test/IntegrationTest.java @@ -40,9 +40,11 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @Target(ElementType.TYPE) // Leave out the ServletTestExecutionListener because it only deals with Mock* servlet // stuff. A real embedded application will not need the mocks. -@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class, +@TestExecutionListeners(listeners = { EmbeddedServletContainerListener.class, + DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class }) public @interface IntegrationTest { + String[] value() default ""; } diff --git a/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java b/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java index 9c0734d484..2be5d12205 100644 --- a/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java +++ b/spring-boot/src/main/java/org/springframework/boot/test/SpringApplicationContextLoader.java @@ -18,6 +18,7 @@ package org.springframework.boot.test; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -135,16 +136,32 @@ public class SpringApplicationContextLoader extends AbstractContextLoader { private Map<String, Object> getArgs(MergedContextConfiguration mergedConfig) { Map<String, Object> args = new LinkedHashMap<String, Object>(); - if (AnnotationUtils.findAnnotation(mergedConfig.getTestClass(), - IntegrationTest.class) == null) { + // JMX bean names will clash if the same bean is used in multiple contexts + args.put("spring.jmx.enabled", "false"); + IntegrationTest annotation = AnnotationUtils.findAnnotation( + mergedConfig.getTestClass(), IntegrationTest.class); + if (annotation == null) { // Not running an embedded server, just setting up web context args.put("server.port", "-1"); } - // JMX bean names will clash if the same bean is used in multiple contexts - args.put("spring.jmx.enabled", "false"); + else { + args.putAll(extractProperties(annotation.value())); + } return args; } + private Map<String, String> extractProperties(String[] values) { + Map<String, String> map = new HashMap<String, String>(); + for (String pair : values) { + int index = pair.indexOf(":"); + index = index < 0 ? index = pair.indexOf("=") : index; + String key = pair.substring(0, index > 0 ? index : pair.length()); + String value = index > 0 ? pair.substring(index + 1) : ""; + map.put(key.trim(), value.trim()); + } + return map; + } + private List<ApplicationContextInitializer<?>> getInitializers( MergedContextConfiguration mergedConfig, SpringApplication application) { List<ApplicationContextInitializer<?>> initializers = new ArrayList<ApplicationContextInitializer<?>>(); diff --git a/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationIntegrationTestTests.java b/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationIntegrationTestTests.java index 507433137b..d9e86ef280 100644 --- a/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationIntegrationTestTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/test/SpringApplicationIntegrationTestTests.java @@ -18,11 +18,13 @@ package org.springframework.boot.test; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.test.SpringApplicationIntegrationTestTests.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.bind.annotation.RequestMapping; @@ -32,6 +34,7 @@ import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; /** * Tests for {@link IntegrationTest} @@ -41,13 +44,18 @@ import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Config.class) @WebAppConfiguration -@IntegrationTest +@IntegrationTest("server.port=0") public class SpringApplicationIntegrationTestTests { + @Value("${local.server.port}") + private int port = 0; + @Test public void runAndTestHttpEndpoint() { - String body = new RestTemplate().getForObject("http://localhost:8080/", - String.class); + assertNotEquals(8080, this.port); + assertNotEquals(0, this.port); + String body = new RestTemplate().getForObject("http://localhost:" + this.port + + "/", String.class); assertEquals("Hello World", body); } @@ -56,6 +64,9 @@ public class SpringApplicationIntegrationTestTests { @RestController protected static class Config { + @Value("${server.port:8080}") + private int port = 8080; + @Bean public DispatcherServlet dispatcherServlet() { return new DispatcherServlet(); @@ -63,7 +74,14 @@ public class SpringApplicationIntegrationTestTests { @Bean public EmbeddedServletContainerFactory embeddedServletContainer() { - return new TomcatEmbeddedServletContainerFactory(); + TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); + factory.setPort(this.port); + return factory; + } + + @Bean + public static PropertySourcesPlaceholderConfigurer propertyPlaceholder() { + return new PropertySourcesPlaceholderConfigurer(); } @RequestMapping("/")