diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index 3fb2f76fa2..4ad586d157 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -76,6 +76,7 @@
spring-boot-sample-servlet
spring-boot-sample-session-redis
spring-boot-sample-simple
+ spring-boot-sample-test
spring-boot-sample-testng
spring-boot-sample-tomcat
spring-boot-sample-tomcat-jsp
diff --git a/spring-boot-samples/spring-boot-sample-test/pom.xml b/spring-boot-samples/spring-boot-sample-test/pom.xml
new file mode 100644
index 0000000000..e54c05f06b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.4.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-test
+ Spring Boot Test Sample
+ Spring Boot Test Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+
+
+ mysql
+ mysql-connector-java
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.seleniumhq.selenium
+ selenium-htmlunit-driver
+ test
+
+
+ net.sourceforge.htmlunit
+ htmlunit
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/SampleTestApplication.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/SampleTestApplication.java
new file mode 100644
index 0000000000..782c35b92a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/SampleTestApplication.java
@@ -0,0 +1,37 @@
+/*
+ * 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 sample.test;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Sample application to demonstrate testing.
+ *
+ * @author Phillip Webb
+ */
+@SpringBootApplication
+public class SampleTestApplication {
+
+ // NOTE: this application will intentionally not start without MySQL, the test will
+ // still run.
+
+ public static void main(String[] args) {
+ SpringApplication.run(SampleTestApplication.class, args);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/WelcomeCommandLineRunner.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/WelcomeCommandLineRunner.java
new file mode 100644
index 0000000000..5f677a3f44
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/WelcomeCommandLineRunner.java
@@ -0,0 +1,36 @@
+/*
+ * 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 sample.test;
+
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+/**
+ * Simple component that just prints a message. Used to show how different types of
+ * integration tests work.
+ *
+ * @author Phillip Webb
+ */
+@Component
+public class WelcomeCommandLineRunner implements CommandLineRunner {
+
+ @Override
+ public void run(String... args) throws Exception {
+ System.out.println("***** WELCOME TO THE DEMO *****");
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/User.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/User.java
new file mode 100644
index 0000000000..51549f65cd
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/User.java
@@ -0,0 +1,65 @@
+/*
+ * 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 sample.test.domain;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+import org.springframework.util.Assert;
+
+/**
+ * A user of the system.
+ *
+ * @author Phillip Webb
+ */
+@Entity
+public class User {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ @Column(unique = true)
+ private String username;
+
+ private VehicleIdentificationNumber vin;
+
+ protected User() {
+ }
+
+ public User(String username, VehicleIdentificationNumber vin) {
+ Assert.hasLength(username, "Username must not be empty");
+ Assert.notNull(vin, "VIN must not be null");
+ this.username = username;
+ this.vin = vin;
+ }
+
+ protected Long getId() {
+ return this.id;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ public VehicleIdentificationNumber getVin() {
+ return this.vin;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/UserRepository.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/UserRepository.java
new file mode 100644
index 0000000000..12d1e559d4
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/UserRepository.java
@@ -0,0 +1,30 @@
+/*
+ * 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 sample.test.domain;
+
+import org.springframework.data.repository.Repository;
+
+/**
+ * Domain repository for {@link User}
+ *
+ * @author Phillip Webb
+ */
+public interface UserRepository extends Repository {
+
+ User findByUsername(String username);
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumber.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumber.java
new file mode 100644
index 0000000000..c3ac249fe8
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumber.java
@@ -0,0 +1,57 @@
+/*
+ * 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 sample.test.domain;
+
+import org.springframework.util.Assert;
+
+/**
+ * A Vehicle Identification Number.
+ *
+ * @author Phillip Webb
+ */
+public final class VehicleIdentificationNumber {
+
+ private String vin;
+
+ public VehicleIdentificationNumber(String vin) {
+ Assert.notNull(vin, "VIN must not be null");
+ Assert.isTrue(vin.length() == 17, "VIN must be exactly 17 characters");
+ this.vin = vin;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.vin.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ return this.vin.equals(((VehicleIdentificationNumber) obj).vin);
+ }
+
+ @Override
+ public String toString() {
+ return this.vin;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumberAttributeConverter.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumberAttributeConverter.java
new file mode 100644
index 0000000000..3cdf0515b5
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/domain/VehicleIdentificationNumberAttributeConverter.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 sample.test.domain;
+
+import javax.persistence.AttributeConverter;
+import javax.persistence.Converter;
+
+/**
+ * JPA {@link AttributeConverter} for {@link VehicleIdentificationNumber}.
+ *
+ * @author Phillip Webb
+ */
+@Converter(autoApply = true)
+public class VehicleIdentificationNumberAttributeConverter
+ implements AttributeConverter {
+
+ @Override
+ public String convertToDatabaseColumn(VehicleIdentificationNumber attribute) {
+ return attribute.toString();
+ }
+
+ @Override
+ public VehicleIdentificationNumber convertToEntityAttribute(String dbData) {
+ return new VehicleIdentificationNumber(dbData);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/RemoteVehicleDetailsService.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/RemoteVehicleDetailsService.java
new file mode 100644
index 0000000000..0a99173501
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/RemoteVehicleDetailsService.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 sample.test.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sample.test.domain.VehicleIdentificationNumber;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Assert;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * {@link VehicleDetailsService} backed by a remote REST service.
+ *
+ * @author Phillip Webb
+ */
+@Service
+public class RemoteVehicleDetailsService implements VehicleDetailsService {
+
+ private static final Logger logger = LoggerFactory
+ .getLogger(RemoteVehicleDetailsService.class);
+
+ private final ServiceProperties properties;
+
+ private final RestTemplate restTemplate;
+
+ public RemoteVehicleDetailsService(ServiceProperties properties) {
+ this.properties = properties;
+ this.restTemplate = new RestTemplate();
+ }
+
+ protected final RestTemplate getRestTemplate() {
+ return this.restTemplate;
+ }
+
+ @Override
+ public VehicleDetails getVehicleDetails(VehicleIdentificationNumber vin)
+ throws VehicleIdentificationNumberNotFoundException {
+ Assert.notNull(vin, "VIN must not be null");
+ String url = this.properties.getVehicleServiceRootUrl() + "vehicle/{vin}/details";
+ logger.debug("Retrieving vehicle data for: " + vin + " from: " + url);
+ try {
+ return this.restTemplate.getForObject(url, VehicleDetails.class, vin);
+ }
+ catch (HttpStatusCodeException ex) {
+ if (HttpStatus.NOT_FOUND.equals(ex.getStatusCode())) {
+ throw new VehicleIdentificationNumberNotFoundException(vin, ex);
+ }
+ throw ex;
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/ServiceProperties.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/ServiceProperties.java
new file mode 100644
index 0000000000..9222fbd46f
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/ServiceProperties.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 sample.test.service;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Properties for the service.
+ *
+ * @author Phillip Webb
+ */
+@Component
+@ConfigurationProperties
+public class ServiceProperties {
+
+ private String vehicleServiceRootUrl = "http://localhost:8080/vs/";
+
+ public String getVehicleServiceRootUrl() {
+ return this.vehicleServiceRootUrl;
+ }
+
+ public void setVehicleServiceRootUrl(String vehicleServiceRootUrl) {
+ this.vehicleServiceRootUrl = vehicleServiceRootUrl;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetails.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetails.java
new file mode 100644
index 0000000000..e0515ff56d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetails.java
@@ -0,0 +1,69 @@
+/*
+ * 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 sample.test.service;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import org.springframework.util.Assert;
+
+/**
+ * Details of a single vehicle.
+ *
+ * @author Phillip Webb
+ */
+public class VehicleDetails {
+
+ private final String make;
+
+ private final String model;
+
+ @JsonCreator
+ public VehicleDetails(@JsonProperty("make") String make,
+ @JsonProperty("model") String model) {
+ Assert.notNull(make, "Make must not be null");
+ Assert.notNull(model, "Model must not be null");
+ this.make = make;
+ this.model = model;
+ }
+
+ public String getMake() {
+ return this.make;
+ }
+
+ public String getModel() {
+ return this.model;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.make.hashCode() * 31 + this.model.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != getClass()) {
+ return false;
+ }
+ VehicleDetails other = (VehicleDetails) obj;
+ return this.make.equals(other.make) && this.model.equals(other.model);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetailsService.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetailsService.java
new file mode 100644
index 0000000000..326b1fb85b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleDetailsService.java
@@ -0,0 +1,37 @@
+/*
+ * 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 sample.test.service;
+
+import sample.test.domain.VehicleIdentificationNumber;
+
+/**
+ * A service to obtain {@link VehicleDetails} given a {@link VehicleIdentificationNumber}.
+ *
+ * @author Phillip Webb
+ */
+public interface VehicleDetailsService {
+
+ /**
+ * Get vehicle details for a given {@link VehicleIdentificationNumber}.
+ * @param vin the vehicle identification number
+ * @return vehicle details
+ * @throws VehicleIdentificationNumberNotFoundException if the VIN is not known
+ */
+ VehicleDetails getVehicleDetails(VehicleIdentificationNumber vin)
+ throws VehicleIdentificationNumberNotFoundException;
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleIdentificationNumberNotFoundException.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleIdentificationNumberNotFoundException.java
new file mode 100644
index 0000000000..cfa7b59047
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/service/VehicleIdentificationNumberNotFoundException.java
@@ -0,0 +1,43 @@
+/*
+ * 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 sample.test.service;
+
+import sample.test.domain.VehicleIdentificationNumber;
+
+/**
+ * Exception thrown when a {@link VehicleIdentificationNumber} is not found.
+ *
+ * @author Phillip Webb
+ */
+public class VehicleIdentificationNumberNotFoundException extends RuntimeException {
+
+ private VehicleIdentificationNumber vehicleIdentificationNumber;
+
+ public VehicleIdentificationNumberNotFoundException(VehicleIdentificationNumber vin) {
+ this(vin, null);
+ }
+
+ public VehicleIdentificationNumberNotFoundException(VehicleIdentificationNumber vin,
+ Throwable cause) {
+ super("Unable to find VehicleIdentificationNumber " + vin, cause);
+ this.vehicleIdentificationNumber = vin;
+ }
+
+ public VehicleIdentificationNumber getVehicleIdentificationNumber() {
+ return this.vehicleIdentificationNumber;
+ }
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserNameNotFoundException.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserNameNotFoundException.java
new file mode 100644
index 0000000000..4c4a1e680b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserNameNotFoundException.java
@@ -0,0 +1,35 @@
+/*
+ * 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 sample.test.web;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.NOT_FOUND)
+public class UserNameNotFoundException extends RuntimeException {
+
+ private String username;
+
+ public UserNameNotFoundException(String username) {
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleController.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleController.java
new file mode 100644
index 0000000000..9085ec6b6d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleController.java
@@ -0,0 +1,68 @@
+/*
+ * 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 sample.test.web;
+
+import sample.test.domain.User;
+import sample.test.service.VehicleDetails;
+import sample.test.service.VehicleIdentificationNumberNotFoundException;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Controller to return vehicle information for a given {@link User}
+ *
+ * @author Phillip Webb
+ */
+@RestController
+public class UserVehicleController {
+
+ private UserVehicleService userVehicleService;
+
+ public UserVehicleController(UserVehicleService userVehicleService) {
+ this.userVehicleService = userVehicleService;
+ }
+
+ @GetMapping(path = "/{username}/vehicle", produces = MediaType.TEXT_PLAIN_VALUE)
+ public String getVehicleDetailsText(@PathVariable String username) {
+ VehicleDetails details = this.userVehicleService.getVehicleDetails(username);
+ return details.getMake() + " " + details.getModel();
+ }
+
+ @GetMapping(path = "/{username}/vehicle", produces = MediaType.APPLICATION_JSON_VALUE)
+ public VehicleDetails VehicleDetailsJson(@PathVariable String username) {
+ return this.userVehicleService.getVehicleDetails(username);
+ }
+
+ @GetMapping(path = "/{username}/vehicle.html", produces = MediaType.TEXT_HTML_VALUE)
+ public String VehicleDetailsHtml(@PathVariable String username) {
+ VehicleDetails details = this.userVehicleService.getVehicleDetails(username);
+ String makeAndModel = details.getMake() + " " + details.getModel();
+ return "" + makeAndModel + "
";
+ }
+
+ @ExceptionHandler
+ @ResponseStatus(HttpStatus.NOT_FOUND)
+ private void handleVinNotFound(VehicleIdentificationNumberNotFoundException ex) {
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleService.java b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleService.java
new file mode 100644
index 0000000000..1ac6443f0a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/java/sample/test/web/UserVehicleService.java
@@ -0,0 +1,57 @@
+/*
+ * 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 sample.test.web;
+
+import sample.test.domain.User;
+import sample.test.domain.UserRepository;
+import sample.test.service.VehicleDetails;
+import sample.test.service.VehicleDetailsService;
+import sample.test.service.VehicleIdentificationNumberNotFoundException;
+
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+/**
+ * Controller service used to provide vehicle information for a given user.
+ *
+ * @author Phillip Webb
+ */
+@Component
+public class UserVehicleService {
+
+ private final UserRepository userRepository;
+
+ private final VehicleDetailsService vehicleDetailsService;
+
+ public UserVehicleService(UserRepository userRepository,
+ VehicleDetailsService vehicleDetailsService) {
+ this.userRepository = userRepository;
+ this.vehicleDetailsService = vehicleDetailsService;
+ }
+
+ public VehicleDetails getVehicleDetails(String username)
+ throws UserNameNotFoundException,
+ VehicleIdentificationNumberNotFoundException {
+ Assert.notNull(username, "Username must not be null");
+ User user = this.userRepository.findByUsername(username);
+ if (user == null) {
+ throw new UserNameNotFoundException(username);
+ }
+ return this.vehicleDetailsService.getVehicleDetails(user.getVin());
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-test/src/main/resources/application.properties
new file mode 100644
index 0000000000..c28dbb20bc
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.datasource.url=jdbc:mysql://localhost/doesnotexist
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/SampleTestApplicationWebIntegrationTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/SampleTestApplicationWebIntegrationTests.java
new file mode 100644
index 0000000000..94801f81c4
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/SampleTestApplicationWebIntegrationTests.java
@@ -0,0 +1,65 @@
+/*
+ * 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 sample.test;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import sample.test.domain.VehicleIdentificationNumber;
+import sample.test.service.VehicleDetails;
+import sample.test.service.VehicleDetailsService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase;
+import org.springframework.boot.test.context.web.WebIntegrationTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.mockito.BDDMockito.given;
+
+/**
+ * {@code @WebIntegrationTest} for {@link SampleTestApplication}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@WebIntegrationTest(randomPort = true)
+@AutoConfigureTestDatabase
+public class SampleTestApplicationWebIntegrationTests {
+
+ private static final VehicleIdentificationNumber VIN = new VehicleIdentificationNumber(
+ "01234567890123456");
+
+ @Autowired
+ private TestRestTemplate restTemplate;
+
+ @MockBean
+ private VehicleDetailsService vehicleDetailsService;
+
+ @Before
+ public void setup() {
+ given(this.vehicleDetailsService.getVehicleDetails(VIN))
+ .willReturn(new VehicleDetails("Honda", "Civic"));
+ }
+
+ @Test
+ public void test() {
+ this.restTemplate.getForEntity("/{username}/vehicle", String.class, "sframework");
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserEntityTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserEntityTests.java
new file mode 100644
index 0000000000..afa48727c9
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserEntityTests.java
@@ -0,0 +1,77 @@
+/*
+ * 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 sample.test.domain;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Data JPA tests for {@link User}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@DataJpaTest
+public class UserEntityTests {
+
+ private static final VehicleIdentificationNumber VIN = new VehicleIdentificationNumber(
+ "00000000000000000");
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Test
+ public void createWhenUserIdIsNullShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Username must not be empty");
+ new User(null, VIN);
+ }
+
+ @Test
+ public void createWhenUserIdIsEmptyShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Username must not be empty");
+ new User("", VIN);
+ }
+
+ @Test
+ public void createWhenVinIsNullShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("VIN must not be null");
+ new User("sboot", null);
+ }
+
+ @Test
+ public void saveShouldPersistData() throws Exception {
+ User user = this.entityManager.persistFlushFind(new User("sboot", VIN));
+ assertThat(user.getUsername()).isEqualTo("sboot");
+ assertThat(user.getVin()).isEqualTo(VIN);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserRepositoryTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserRepositoryTests.java
new file mode 100644
index 0000000000..b61f2d0bbc
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/UserRepositoryTests.java
@@ -0,0 +1,62 @@
+/*
+ * 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 sample.test.domain;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link UserRepository}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@DataJpaTest
+public class UserRepositoryTests {
+
+ private static final VehicleIdentificationNumber VIN = new VehicleIdentificationNumber(
+ "00000000000000000");
+
+ @Autowired
+ private TestEntityManager entityManager;
+
+ @Autowired
+ private UserRepository repository;
+
+ @Test
+ public void findByUsernameShouldReturnUser() throws Exception {
+ this.entityManager.persist(new User("sboot", VIN));
+ User user = this.repository.findByUsername("sboot");
+ assertThat(user.getUsername()).isEqualTo("sboot");
+ assertThat(user.getVin()).isEqualTo(VIN);
+ }
+
+ @Test
+ public void findByUsernameWhenNoUserShouldReturnNull() throws Exception {
+ this.entityManager.persist(new User("sboot", VIN));
+ User user = this.repository.findByUsername("mmouse");
+ assertThat(user).isNull();
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/VehicleIdentificationNumberTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/VehicleIdentificationNumberTests.java
new file mode 100644
index 0000000000..ede0046ee5
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/domain/VehicleIdentificationNumberTests.java
@@ -0,0 +1,77 @@
+/*
+ * 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 sample.test.domain;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link VehicleIdentificationNumber}.
+ *
+ * @author Phillip Webb
+ * @see
+ * Naming standards for unit tests
+ * @see AssertJ
+ */
+public class VehicleIdentificationNumberTests {
+
+ private static final String SAMPLE_VIN = "41549485710496749";
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void createWhenVinIsNullShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("VIN must not be null");
+ new VehicleIdentificationNumber(null);
+ }
+
+ @Test
+ public void createWhenVinIsMoreThan17CharsShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("VIN must be exactly 17 characters");
+ new VehicleIdentificationNumber("012345678901234567");
+ }
+
+ @Test
+ public void createWhenVinIsLessThan17CharsShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("VIN must be exactly 17 characters");
+ new VehicleIdentificationNumber("0123456789012345");
+ }
+
+ @Test
+ public void toStringShouldReturnVin() throws Exception {
+ VehicleIdentificationNumber vin = new VehicleIdentificationNumber(SAMPLE_VIN);
+ assertThat(vin.toString()).isEqualTo(SAMPLE_VIN);
+ }
+
+ @Test
+ public void equalsAndHashShouldBeBasedOnVin() throws Exception {
+ VehicleIdentificationNumber vin1 = new VehicleIdentificationNumber(SAMPLE_VIN);
+ VehicleIdentificationNumber vin2 = new VehicleIdentificationNumber(SAMPLE_VIN);
+ VehicleIdentificationNumber vin3 = new VehicleIdentificationNumber(
+ "00000000000000000");
+ assertThat(vin1.hashCode()).isEqualTo(vin2.hashCode());
+ assertThat(vin1).isEqualTo(vin1).isEqualTo(vin2).isNotEqualTo(vin3);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/RemoteVehicleDetailsServiceTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/RemoteVehicleDetailsServiceTests.java
new file mode 100644
index 0000000000..4216f56b00
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/RemoteVehicleDetailsServiceTests.java
@@ -0,0 +1,102 @@
+/*
+ * 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 sample.test.service;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import sample.test.domain.VehicleIdentificationNumber;
+
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.client.MockRestServiceServer;
+import org.springframework.web.client.HttpServerErrorException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
+
+/**
+ * Tests for {@link RemoteVehicleDetailsService}.
+ *
+ * @author Phillip Webb
+ */
+public class RemoteVehicleDetailsServiceTests {
+
+ private static final String VIN = "00000000000000000";
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private RemoteVehicleDetailsService service;
+
+ private MockRestServiceServer server;
+
+ @Before
+ public void setup() {
+ ServiceProperties properties = new ServiceProperties();
+ properties.setVehicleServiceRootUrl("http://example.com/");
+ this.service = new RemoteVehicleDetailsService(properties);
+ this.server = MockRestServiceServer.createServer(this.service.getRestTemplate());
+ }
+
+ @Test
+ public void getVehicleDetailsWhenVinIsNullShouldThrowException() throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("VIN must not be null");
+ this.service.getVehicleDetails(null);
+ }
+
+ @Test
+ public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails()
+ throws Exception {
+ this.server.expect(requestTo("http://example.com/vehicle/" + VIN + "/details"))
+ .andRespond(withSuccess(getClassPathResource("vehicledetails.json"),
+ MediaType.APPLICATION_JSON));
+ VehicleDetails details = this.service
+ .getVehicleDetails(new VehicleIdentificationNumber(VIN));
+ assertThat(details.getMake()).isEqualTo("Honda");
+ assertThat(details.getModel()).isEqualTo("Civic");
+ }
+
+ @Test
+ public void getVehicleDetailsWhenResultIsNotFoundShouldThrowException()
+ throws Exception {
+ this.server.expect(requestTo("http://example.com/vehicle/" + VIN + "/details"))
+ .andRespond(withStatus(HttpStatus.NOT_FOUND));
+ this.thrown.expect(VehicleIdentificationNumberNotFoundException.class);
+ this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN));
+ }
+
+ @Test
+ public void getVehicleDetailsWhenResultIServerErrorShouldThrowException()
+ throws Exception {
+ this.server.expect(requestTo("http://example.com/vehicle/" + VIN + "/details"))
+ .andRespond(withServerError());
+ this.thrown.expect(HttpServerErrorException.class);
+ this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN));
+ }
+
+ private ClassPathResource getClassPathResource(String path) {
+ return new ClassPathResource(path, getClass());
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java
new file mode 100644
index 0000000000..89848ff487
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/service/VehicleDetailsJsonTests.java
@@ -0,0 +1,57 @@
+/*
+ * 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 sample.test.service;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.springframework.boot.test.autoconfigure.json.JsonTest;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * JSON Tests for {@link VehicleDetails}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@JsonTest
+public class VehicleDetailsJsonTests {
+
+ private JacksonTester json;
+
+ @Test
+ public void serializeJson() throws Exception {
+ VehicleDetails details = new VehicleDetails("Honda", "Civic");
+ assertThat(this.json.write(details)).isEqualTo("vehicledetails.json");
+ assertThat(this.json.write(details)).isEqualToJson("vehicledetails.json");
+ assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
+ assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make")
+ .isEqualTo("Honda");
+ }
+
+ @Test
+ public void deserializeJson() 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");
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerApplicationTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerApplicationTests.java
new file mode 100644
index 0000000000..12732c802b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerApplicationTests.java
@@ -0,0 +1,75 @@
+/*
+ * 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 sample.test.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import sample.test.WelcomeCommandLineRunner;
+import sample.test.service.VehicleDetails;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringApplicationTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * {@code @SpringApplicationTest} based tests for {@link UserVehicleController}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@SpringApplicationTest
+@AutoConfigureMockMvc
+@AutoConfigureTestDatabase
+public class UserVehicleControllerApplicationTests {
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @MockBean
+ private UserVehicleService userVehicleService;
+
+ @Test
+ public void getVehicleWhenRequestingTextShouldReturnMakeAndModel() 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"));
+ }
+
+ @Test
+ public void welcomeCommandLineRunnerShouldBeAvailble() throws Exception {
+ // Since we're a @SpringApplicationTest all beans should be available
+ assertThat(this.applicationContext.getBean(WelcomeCommandLineRunner.class))
+ .isNotNull();
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerHtmlUnitTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerHtmlUnitTests.java
new file mode 100644
index 0000000000..2aee09405e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerHtmlUnitTests.java
@@ -0,0 +1,56 @@
+/*
+ * 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 sample.test.web;
+
+import com.gargoylesoftware.htmlunit.WebClient;
+import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import sample.test.service.VehicleDetails;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+
+/**
+ * HtmlUnit based tests for {@link UserVehicleController}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@WebMvcTest(UserVehicleController.class)
+public class UserVehicleControllerHtmlUnitTests {
+
+ @Autowired
+ private WebClient webClient;
+
+ @MockBean
+ private UserVehicleService userVehicleService;
+
+ @Test
+ public void getVehicleWhenRequestingTextShouldReturnMakeAndModel() 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");
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerSeleniumTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerSeleniumTests.java
new file mode 100644
index 0000000000..ca54240f75
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerSeleniumTests.java
@@ -0,0 +1,58 @@
+/*
+ * 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 sample.test.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import sample.test.service.VehicleDetails;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+
+/**
+ * Selenium based tests for {@link UserVehicleController}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@WebMvcTest(UserVehicleController.class)
+public class UserVehicleControllerSeleniumTests {
+
+ @Autowired
+ private WebDriver webDriver;
+
+ @MockBean
+ private UserVehicleService userVehicleService;
+
+ @Test
+ public void getVehicleWhenRequestingTextShouldReturnMakeAndModel() throws Exception {
+ given(this.userVehicleService.getVehicleDetails("sboot"))
+ .willReturn(new VehicleDetails("Honda", "Civic"));
+ this.webDriver.get("/sboot/vehicle.html");
+ WebElement element = this.webDriver.findElement(By.tagName("h1"));
+ assertThat(element.getText()).isEqualTo("Honda Civic");
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerTests.java
new file mode 100644
index 0000000000..8a4575053a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleControllerTests.java
@@ -0,0 +1,109 @@
+/*
+ * 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 sample.test.web;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import sample.test.WelcomeCommandLineRunner;
+import sample.test.domain.VehicleIdentificationNumber;
+import sample.test.service.VehicleDetails;
+import sample.test.service.VehicleIdentificationNumberNotFoundException;
+
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.mockito.BDDMockito.given;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * {@code @WebMvcTest} based tests for {@link UserVehicleController}.
+ *
+ * @author Phillip Webb
+ */
+@RunWith(SpringRunner.class)
+@WebMvcTest(UserVehicleController.class)
+public class UserVehicleControllerTests {
+
+ private static final VehicleIdentificationNumber VIN = new VehicleIdentificationNumber(
+ "00000000000000000");
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @MockBean
+ private UserVehicleService userVehicleService;
+
+ @Test
+ public void getVehicleWhenRequestingTextShouldReturnMakeAndModel() 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"));
+ }
+
+ @Test
+ public void getVehicleWhenRequestingJsonShouldReturnMakeAndModel() throws Exception {
+ given(this.userVehicleService.getVehicleDetails("sboot"))
+ .willReturn(new VehicleDetails("Honda", "Civic"));
+ this.mvc.perform(get("/sboot/vehicle").accept(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk())
+ .andExpect(content().json("{'make':'Honda','model':'Civic'}"));
+ }
+
+ @Test
+ public void getVehicleWhenRequestingHtmlShouldReturnMakeAndModel() throws Exception {
+ given(this.userVehicleService.getVehicleDetails("sboot"))
+ .willReturn(new VehicleDetails("Honda", "Civic"));
+ this.mvc.perform(get("/sboot/vehicle.html").accept(MediaType.TEXT_HTML))
+ .andExpect(status().isOk())
+ .andExpect(content().string(containsString("Honda Civic
")));
+ }
+
+ @Test
+ public void getVehicleWhenUserNotFoundShouldReturnNotFound() throws Exception {
+ given(this.userVehicleService.getVehicleDetails("sboot"))
+ .willThrow(new UserNameNotFoundException("sboot"));
+ this.mvc.perform(get("/sboot/vehicle")).andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void getVehicleWhenVinNotFoundShouldReturnNotFound() throws Exception {
+ given(this.userVehicleService.getVehicleDetails("sboot"))
+ .willThrow(new VehicleIdentificationNumberNotFoundException(VIN));
+ this.mvc.perform(get("/sboot/vehicle")).andExpect(status().isNotFound());
+ }
+
+ @Test(expected = NoSuchBeanDefinitionException.class)
+ public void welcomeCommandLineRunnerShouldBeAvailble() throws Exception {
+ // Since we're a @WebMvcTest WelcomeCommandLineRunner should not be available
+ assertThat(this.applicationContext.getBean(WelcomeCommandLineRunner.class));
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleServiceTests.java b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleServiceTests.java
new file mode 100644
index 0000000000..ce32fdc1a7
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/java/sample/test/web/UserVehicleServiceTests.java
@@ -0,0 +1,89 @@
+/*
+ * 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 sample.test.web;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import sample.test.domain.User;
+import sample.test.domain.UserRepository;
+import sample.test.domain.VehicleIdentificationNumber;
+import sample.test.service.VehicleDetails;
+import sample.test.service.VehicleDetailsService;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Matchers.anyString;
+
+/**
+ * Tests for {@link UserVehicleService}.
+ *
+ * @author Phillip Webb
+ */
+public class UserVehicleServiceTests {
+
+ private static final VehicleIdentificationNumber VIN = new VehicleIdentificationNumber(
+ "00000000000000000");
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Mock
+ private VehicleDetailsService vehicleDetailsService;
+
+ @Mock
+ private UserRepository userRepository;
+
+ private UserVehicleService service;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ this.service = new UserVehicleService(this.userRepository,
+ this.vehicleDetailsService);
+ }
+
+ @Test
+ public void getVehicleDetailsWhenUsernameIsNullShouldThrowException()
+ throws Exception {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Username must not be null");
+ this.service.getVehicleDetails(null);
+ }
+
+ @Test
+ public void getVehicleDetailsWhenUserNameNotFoundShouldThrowException()
+ throws Exception {
+ given(this.userRepository.findByUsername(anyString())).willReturn(null);
+ this.thrown.expect(UserNameNotFoundException.class);
+ this.service.getVehicleDetails("sboot");
+ }
+
+ @Test
+ public void getVehicleDetailsShouldReturnMakeAndModel() throws Exception {
+ given(this.userRepository.findByUsername(anyString()))
+ .willReturn(new User("sboot", VIN));
+ VehicleDetails details = new VehicleDetails("Honda", "Civic");
+ given(this.vehicleDetailsService.getVehicleDetails(VIN)).willReturn(details);
+ VehicleDetails actual = this.service.getVehicleDetails("sboot");
+ assertThat(actual).isEqualTo(details);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/resources/data.sql b/spring-boot-samples/spring-boot-sample-test/src/test/resources/data.sql
new file mode 100644
index 0000000000..70ecc99a37
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/resources/data.sql
@@ -0,0 +1 @@
+INSERT INTO USER(ID, USERNAME, VIN) values (123, 'sframework', '01234567890123456');
diff --git a/spring-boot-samples/spring-boot-sample-test/src/test/resources/sample/test/service/vehicledetails.json b/spring-boot-samples/spring-boot-sample-test/src/test/resources/sample/test/service/vehicledetails.json
new file mode 100644
index 0000000000..b0a453d566
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-test/src/test/resources/sample/test/service/vehicledetails.json
@@ -0,0 +1,4 @@
+{
+ "make" : "Honda",
+ "model" : "Civic"
+}