diff --git a/spring-boot-project/spring-boot-parent/pom.xml b/spring-boot-project/spring-boot-parent/pom.xml
index 5815ed0a5634..54cf00fe003c 100644
--- a/spring-boot-project/spring-boot-parent/pom.xml
+++ b/spring-boot-project/spring-boot-parent/pom.xml
@@ -53,6 +53,25 @@
log4j
1.2.17
+
+ com.nhaarman
+ mockito-kotlin
+ 1.5.0
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+
+
+ org.mockito
+ mockito-core
+
+
+
com.squareup.okhttp
okhttp
diff --git a/spring-boot-project/spring-boot-test/pom.xml b/spring-boot-project/spring-boot-test/pom.xml
index 2c98894a124b..1ea05b865286 100644
--- a/spring-boot-project/spring-boot-test/pom.xml
+++ b/spring-boot-project/spring-boot-test/pom.xml
@@ -75,6 +75,16 @@
hamcrest-library
true
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ true
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ true
+
org.mockito
mockito-core
@@ -182,6 +192,11 @@
junit-jupiter-api
test
+
+ com.nhaarman
+ mockito-kotlin
+ test
+
@@ -196,6 +211,66 @@
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ ${project.basedir}/src/main/kotlin
+ ${project.basedir}/src/main/java
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+ ${project.basedir}/src/test/kotlin
+ ${project.basedir}/src/test/java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
diff --git a/spring-boot-project/spring-boot-test/src/main/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensions.kt b/spring-boot-project/spring-boot-test/src/main/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensions.kt
new file mode 100644
index 000000000000..747fae857286
--- /dev/null
+++ b/spring-boot-project/spring-boot-test/src/main/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensions.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2012-2017 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.web.client
+
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpMethod
+import org.springframework.http.RequestEntity
+import org.springframework.http.ResponseEntity
+import org.springframework.web.client.RestClientException
+import java.net.URI
+
+/**
+ * Extension for [TestRestTemplate.getForObject] avoiding specifying the type
+ * parameter thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForObject(url: String, vararg uriVariables: Any): T? =
+ getForObject(url, T::class.java, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.getForObject] avoiding specifying the type
+ * parameter thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForObject(url: String, uriVariables: Map): T? =
+ getForObject(url, T::class.java, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.getForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForObject(url: URI): T? =
+ getForObject(url, T::class.java)
+
+/**
+ * Extension for [TestRestTemplate.getForEntity] avoiding requiring the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForEntity(url: URI): ResponseEntity =
+ getForEntity(url, T::class.java)
+
+/**
+ * Extension for [TestRestTemplate.getForEntity] avoiding requiring the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForEntity(url: String, vararg uriVariables: Any): ResponseEntity =
+ getForEntity(url, T::class.java, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.getForEntity] avoiding requiring the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.getForEntity(url: String, uriVariables: Map): ResponseEntity =
+ getForEntity(url, T::class.java, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.patchForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.patchForObject(url: String, request: Any, vararg uriVariables: Any): T? =
+ patchForObject(url, request, T::class.java, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.patchForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.patchForObject(url: String, request: Any, uriVariables: Map): T? =
+ patchForObject(url, request, T::class.java, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.patchForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.patchForObject(url: URI, request: Any): T? =
+ patchForObject(url, request, T::class.java)
+
+/**
+ * Extension for [TestRestTemplate.postForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForObject(url: String, request: Any, vararg uriVariables: Any): T? =
+ postForObject(url, request, T::class.java, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.postForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForObject(url: String, request: Any, uriVariables: Map): T? =
+ postForObject(url, request, T::class.java, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.postForObject] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForObject(url: URI, request: Any): T? =
+ postForObject(url, request, T::class.java)
+
+/**
+ * Extension for [TestRestTemplate.postForEntity] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForEntity(url: String, request: Any, vararg uriVariables: Any): ResponseEntity =
+ postForEntity(url, request, T::class.java, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.postForEntity] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForEntity(url: String, request: Any, uriVariables: Map): ResponseEntity =
+ postForEntity(url, request, T::class.java, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.postForEntity] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.postForEntity(url: URI, request: Any): ResponseEntity =
+ postForEntity(url, request, T::class.java)
+
+/**
+ * Extension for [TestRestTemplate.exchange] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.exchange(url: String, method: HttpMethod, requestEntity: HttpEntity<*>, vararg uriVariables: Any): ResponseEntity =
+ exchange(url, method, requestEntity, object : ParameterizedTypeReference() {}, *uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.exchange] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.exchange(url: String, method: HttpMethod, requestEntity: HttpEntity<*>, uriVariables: Map): ResponseEntity =
+ exchange(url, method, requestEntity, object : ParameterizedTypeReference() {}, uriVariables)
+
+/**
+ * Extension for [TestRestTemplate.exchange] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.exchange(url: URI, method: HttpMethod, requestEntity: HttpEntity<*>): ResponseEntity =
+ exchange(url, method, requestEntity, object : ParameterizedTypeReference() {})
+
+/**
+ * Extension for [TestRestTemplate.exchange] avoiding specifying the type parameter
+ * thanks to Kotlin reified type parameters.
+ *
+ * @author Sebastien Deleuze
+ * @since 2.0.0
+ */
+@Throws(RestClientException::class)
+inline fun TestRestTemplate.exchange(requestEntity: RequestEntity<*>): ResponseEntity =
+ exchange(requestEntity, object : ParameterizedTypeReference() {})
\ No newline at end of file
diff --git a/spring-boot-project/spring-boot-test/src/test/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensionsTests.kt b/spring-boot-project/spring-boot-test/src/test/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensionsTests.kt
new file mode 100644
index 000000000000..d9ae59c7fbd7
--- /dev/null
+++ b/spring-boot-project/spring-boot-test/src/test/kotlin/org/springframework/boot/test/web/client/TestRestTemplateExtensionsTests.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2012-2017 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.web.client
+
+import com.nhaarman.mockito_kotlin.mock
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnitRunner
+import org.springframework.core.ParameterizedTypeReference
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpMethod
+import org.springframework.http.RequestEntity
+import org.springframework.util.ReflectionUtils
+import org.springframework.web.client.RestOperations
+import java.net.URI
+import kotlin.reflect.full.createType
+import kotlin.reflect.jvm.kotlinFunction
+
+/**
+ * Mock object based tests for [TestRestTemplate] Kotlin extensions
+ *
+ * @author Sebastien Deleuze
+ */
+@RunWith(MockitoJUnitRunner::class)
+class TestRestTemplateExtensionsTests {
+
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ lateinit var template: TestRestTemplate
+
+ @Test
+ fun `getForObject with reified type parameters, String and varargs`() {
+ val url = "https://spring.io"
+ val var1 = "var1"
+ val var2 = "var2"
+ template.getForObject(url, var1, var2)
+ template.restTemplate
+ verify(template, times(1)).getForObject(url, Foo::class.java, var1, var2)
+ }
+
+ @Test
+ fun `getForObject with reified type parameters, String and Map`() {
+ val url = "https://spring.io"
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.getForObject(url, vars)
+ verify(template, times(1)).getForObject(url, Foo::class.java, vars)
+ }
+
+ @Test
+ fun `getForObject with reified type parameters and URI`() {
+ val url = URI("https://spring.io")
+ template.getForObject(url)
+ verify(template, times(1)).getForObject(url, Foo::class.java)
+ }
+
+ @Test
+ fun `getForEntity with reified type parameters, String and URI`() {
+ val url = URI("https://spring.io")
+ template.getForEntity(url)
+ verify(template, times(1)).getForEntity(url, Foo::class.java)
+ }
+
+ @Test
+ fun `getForEntity with reified type parameters, String and varargs`() {
+ val url = "https://spring.io"
+ val var1 = "var1"
+ val var2 = "var2"
+ template.getForEntity(url, var1, var2)
+ verify(template, times(1)).getForEntity(url, Foo::class.java, var1, var2)
+ }
+
+ @Test
+ fun `getForEntity with reified type parameters and Map`() {
+ val url = "https://spring.io"
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.getForEntity(url, vars)
+ verify(template, times(1)).getForEntity(url, Foo::class.java, vars)
+ }
+
+ @Test
+ fun `patchForObject with reified type parameters, String and varargs`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val var1 = "var1"
+ val var2 = "var2"
+ template.patchForObject(url, body, var1, var2)
+ verify(template, times(1)).patchForObject(url, body, Foo::class.java, var1, var2)
+ }
+
+ @Test
+ fun `patchForObject with reified type parameters, String and Map`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.patchForObject(url, body, vars)
+ verify(template, times(1)).patchForObject(url, body, Foo::class.java, vars)
+ }
+
+ @Test
+ fun `patchForObject with reified type parameters`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ template.patchForObject(url, body)
+ verify(template, times(1)).patchForObject(url, body, Foo::class.java)
+ }
+
+ @Test
+ fun `postForObject with reified type parameters, String and varargs`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val var1 = "var1"
+ val var2 = "var2"
+ template.postForObject(url, body, var1, var2)
+ verify(template, times(1)).postForObject(url, body, Foo::class.java, var1, var2)
+ }
+
+ @Test
+ fun `postForObject with reified type parameters, String and Map`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.postForObject(url, body, vars)
+ verify(template, times(1)).postForObject(url, body, Foo::class.java, vars)
+ }
+
+ @Test
+ fun `postForObject with reified type parameters`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ template.postForObject(url, body)
+ verify(template, times(1)).postForObject(url, body, Foo::class.java)
+ }
+
+ @Test
+ fun `postForEntity with reified type parameters, String and varargs`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val var1 = "var1"
+ val var2 = "var2"
+ template.postForEntity(url, body, var1, var2)
+ verify(template, times(1)).postForEntity(url, body, Foo::class.java, var1, var2)
+ }
+
+ @Test
+ fun `postForEntity with reified type parameters, String and Map`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.postForEntity(url, body, vars)
+ verify(template, times(1)).postForEntity(url, body, Foo::class.java, vars)
+ }
+
+ @Test
+ fun `postForEntity with reified type parameters`() {
+ val url = "https://spring.io"
+ val body: Any = "body"
+ template.postForEntity(url, body)
+ verify(template, times(1)).postForEntity(url, body, Foo::class.java)
+ }
+
+ @Test
+ fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and varargs`() {
+ val url = "https://spring.io"
+ val method = HttpMethod.GET
+ val entity = mock>()
+ val var1 = "var1"
+ val var2 = "var2"
+ template.exchange>(url, method, entity, var1, var2)
+ verify(template, times(1)).exchange(url, method, entity,
+ object : ParameterizedTypeReference>() {}, var1, var2)
+ }
+
+ @Test
+ fun `exchange with reified type parameters, String, HttpMethod, HttpEntity and Map`() {
+ val url = "https://spring.io"
+ val method = HttpMethod.GET
+ val entity = mock>()
+ val vars = mapOf(Pair("key1", "value1"), Pair("key2", "value2"))
+ template.exchange>(url, method, entity, vars)
+ verify(template, times(1)).exchange(url, method, entity,
+ object : ParameterizedTypeReference>() {}, vars)
+ }
+
+ @Test
+ fun `exchange with reified type parameters, String, HttpMethod, HttpEntity`() {
+ val url = "https://spring.io"
+ val method = HttpMethod.GET
+ val entity = mock>()
+ template.exchange>(url, method, entity)
+ verify(template, times(1)).exchange(url, method, entity,
+ object : ParameterizedTypeReference>() {})
+ }
+
+ @Test
+ fun `exchange with reified type parameters, String, HttpEntity`() {
+ val entity = mock>()
+ template.exchange>(entity)
+ verify(template, times(1)).exchange(entity,
+ object : ParameterizedTypeReference>() {})
+ }
+
+ @Test
+ fun `RestOperations are available`() {
+ val extensions = Class.forName(
+ "org.springframework.boot.test.web.client.TestRestTemplateExtensionsKt")
+ ReflectionUtils.doWithMethods(RestOperations::class.java) { method ->
+ arrayOf(ParameterizedTypeReference::class, Class::class).forEach { kClass ->
+ if (method.parameterTypes.contains(kClass.java)) {
+ val parameters = mutableListOf>(TestRestTemplate::class.java)
+ .apply { addAll(method.parameterTypes.filter { it != kClass.java }) }
+ val f = extensions.getDeclaredMethod(method.name,
+ *parameters.toTypedArray()).kotlinFunction!!
+ Assert.assertEquals(1, f.typeParameters.size)
+ Assert.assertEquals(listOf(Any::class.createType()),
+ f.typeParameters[0].upperBounds)
+ }
+ }
+ }
+ }
+
+ class Foo
+
+}