Commit 559009b8 authored by Dave Syer's avatar Dave Syer

Support for random ports in @IntegrationTest

User can now set up default properties in the Environment using
@IntegrationTest("foo:bar", "x:y") etc. Using "server.port:0" you
can get Tomcat or Jetty to spin up on a random port. We also add
a test listener that populates "local.server.port" with the actual
port the server started on so you can @Value("${local.server.port}")
inject it into the test case.

See gh-607 (this should make the extension of that PR to samples
much easier)
parent 2d16c591
......@@ -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<String> 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("<title>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"));
......
/*
* 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);
}
}
......@@ -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;
}
......
......@@ -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 "";
}
......@@ -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<?>>();
......
......@@ -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("/")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment