diff --git a/README.md b/README.md index 24cef09..dbf99bf 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,25 @@ # Spring Kotlin deepdive This project is designed to show step by step how to migrate from Java to Kotlin with -Spring Boot. +Spring Boot step by step: + * [Step 0](https://github.com/sdeleuze/spring-kotlin-deepdive/): Initial Java project + * [Step 1](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1): Java to Kotlin + * [Step 2](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step2): Spring Boot 2 + * [Step 3](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step3): Spring WebFlux + * [Step 4](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step4): Kotlin routing DSL + +See [Spring Kotlin support documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/languages.html#kotlin) for more details. + +## Step 2: Spring Boot 2 -## [Step 1](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step1): Java to Kotlin +* Spring Data Kay + * No need for `kotlin-noarg` plugin since it supports natively Kotlin immutable classes +* Spring Boot 2 + * `jackson-module-kotlin` and `jackson-datatype-jsr310` provided by default with Jackson starter + * Mustache suffix is already `.mustache` by default +* Null safety +* `@RequestParam` on nullable parameter +* Extensions +* JUnit 5 + `@BeforeAll`/ `@AfterAll` -Code: -* No more semicolon at end of lines -* Type suffixed with colon, as statically typed as Java, optional type inference -* Show how to configure return type inference hints -* Short syntax for declaring properties and initializing them from the primary constructor instead of super verbose mostly auto-generated POJO -* [Data classes](https://kotlinlang.org/docs/reference/data-classes.html) -* Syntax help using naturally immutable classes -* `:` instead of `extends` -* No need for `{ }` for empty classes / interfaces -* `public` by the default -* `fun` to declare functions -* Better lambdas: `{ }` last parameter notation, lambda without collect, `it` default parameter -* Constructor without `new` -* `main()` top level method -* `Utils` class -> [Kotlin extensions](https://kotlinlang.org/docs/reference/extensions.html) -* `.getBody()` -> `.body` -* Meaningful function names between backticks - -Build: -* Dependencies: - * `kotlin-stdlib-jre8` - * `kotlin-reflect` - * `jackson-module-kotlin` -* Plugins: - * `kotlin` - * `kotlin-spring` - * `kotlin-noarg` -* Configure to build Java 8 bytecode - -**[Go to step 2: Spring Boot 2](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step2)** +**[Go to step 3: Spring WebFlux](https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step3)** diff --git a/build.gradle b/build.gradle index 4084986..9c27721 100644 --- a/build.gradle +++ b/build.gradle @@ -1,54 +1,63 @@ buildscript { ext { kotlinVersion = "1.1.51" - springBootVersion = "1.5.7.RELEASE" + springBootVersion = "2.0.0.M5" } repositories { mavenCentral() + maven { url "https://repo.spring.io/milestone" } + maven { url "https://repo.spring.io/snapshot" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") - classpath("org.jetbrains.kotlin:kotlin-noarg:${kotlinVersion}") + classpath("org.junit.platform:junit-platform-gradle-plugin:1.0.1") } } apply plugin: "kotlin" apply plugin: "kotlin-spring" -apply plugin: "kotlin-noarg" apply plugin: "org.springframework.boot" +apply plugin: "io.spring.dependency-management" +apply plugin: "org.junit.platform.gradle.plugin" group = "io.spring" version = "1.0.0-SNAPSHOT" repositories { mavenCentral() -} - -noArg { - annotation("org.springframework.data.mongodb.core.mapping.Document") + maven { url "https://repo.spring.io/milestone" } + maven { url "https://repo.spring.io/snapshot" } } compileKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = ["-Xjsr305=strict"] + } } compileTestKotlin { - kotlinOptions.jvmTarget = "1.8" + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = ["-Xjsr305=strict"] + } } dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib-jre8") compile("org.jetbrains.kotlin:kotlin-reflect") - compile("com.fasterxml.jackson.module:jackson-module-kotlin") compile("org.springframework.boot:spring-boot-starter-actuator") compile("org.springframework.boot:spring-boot-starter-data-mongodb") compile("org.springframework.boot:spring-boot-starter-web") - compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") compile("org.springframework.boot:spring-boot-starter-mustache") compile("com.atlassian.commonmark:commonmark:0.10.0") compile("com.atlassian.commonmark:commonmark-ext-autolink:0.10.0") compile("de.flapdoodle.embed:de.flapdoodle.embed.mongo") runtime("org.springframework.boot:spring-boot-devtools") - testCompile("org.springframework.boot:spring-boot-starter-test") + testCompile("org.springframework.boot:spring-boot-starter-test") { + exclude module: "junit" + } + testCompile("org.junit.jupiter:junit-jupiter-api") + testRuntime("org.junit.jupiter:junit-jupiter-engine") } diff --git a/src/main/kotlin/io/spring/deepdive/Application.kt b/src/main/kotlin/io/spring/deepdive/Application.kt index 6365b88..a1f5e2e 100644 --- a/src/main/kotlin/io/spring/deepdive/Application.kt +++ b/src/main/kotlin/io/spring/deepdive/Application.kt @@ -1,17 +1,17 @@ -package io.spring.deepdive; +package io.spring.deepdive -import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Mustache -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.Bean @SpringBootApplication class Application { @Bean fun mustacheCompiler(templateLoader: Mustache.TemplateLoader) = - Mustache.compiler().escapeHTML(false).withLoader(templateLoader); + Mustache.compiler().escapeHTML(false).withLoader(templateLoader) } diff --git a/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt b/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt index 7209d5e..10e6412 100644 --- a/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt +++ b/src/main/kotlin/io/spring/deepdive/DatabaseInitializer.kt @@ -43,7 +43,7 @@ class DatabaseInitializer(private val userRepository: UserRepository, private va val juergen = User("springjuergen", "Juergen", "Hoeller") val violeta = User("violetagg", "Violeta", "Georgieva", "All views are my own!") - userRepository.save(Arrays.asList(brian, mark, arjen, rossen, sam, seb, simon, stephanem, stephanen, juergen, violeta)) + userRepository.saveAll(Arrays.asList(brian, mark, arjen, rossen, sam, seb, simon, stephanem, stephanen, juergen, violeta)) val reactorTitle = "Reactor Bismuth is out" val reactorPost = Post( @@ -87,6 +87,6 @@ class DatabaseInitializer(private val userRepository: UserRepository, private va LocalDateTime.of(2017, 1, 4, 9, 0) ) - postRepository.save(Arrays.asList(reactorPost, spring5Post, springKotlinPost)) + postRepository.saveAll(Arrays.asList(reactorPost, spring5Post, springKotlinPost)) } } diff --git a/src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt b/src/main/kotlin/io/spring/deepdive/web/HtmlController.kt similarity index 83% rename from src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt rename to src/main/kotlin/io/spring/deepdive/web/HtmlController.kt index 2e930e3..94fea19 100644 --- a/src/main/kotlin/io/spring/deepdive/web/HtmlPages.kt +++ b/src/main/kotlin/io/spring/deepdive/web/HtmlController.kt @@ -22,17 +22,16 @@ import io.spring.deepdive.repository.PostRepository import org.springframework.stereotype.Controller import org.springframework.ui.Model -import org.springframework.util.Assert import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import kotlin.streams.toList @Controller -class HtmlPages(private val postRepository: PostRepository, private val markdownConverter: MarkdownConverter) { +class HtmlController(private val repository: PostRepository, private val markdownConverter: MarkdownConverter) { @GetMapping("/") fun blog(model: Model): String { - val posts = postRepository.findAll() + val posts = repository.findAll() val postDtos = StreamSupport.stream(posts.spliterator(), false).map { it.toDto(markdownConverter) }.toList() model.addAttribute("title", "Blog") model.addAttribute("posts", postDtos) @@ -41,8 +40,7 @@ class HtmlPages(private val postRepository: PostRepository, private val markdown @GetMapping("/{slug}") fun post(@PathVariable slug: String, model: Model): String { - val post = postRepository.findOne(slug) - Assert.notNull(post, "Wrong post slug provided") + val post = repository.findById(slug).orElseThrow { IllegalArgumentException("Wrong post slug provided") } model.addAttribute("post", post.toDto(markdownConverter)) return "post" } diff --git a/src/main/kotlin/io/spring/deepdive/web/PostApi.kt b/src/main/kotlin/io/spring/deepdive/web/PostController.kt similarity index 52% rename from src/main/kotlin/io/spring/deepdive/web/PostApi.kt rename to src/main/kotlin/io/spring/deepdive/web/PostController.kt index 1deb6eb..f3eff1e 100644 --- a/src/main/kotlin/io/spring/deepdive/web/PostApi.kt +++ b/src/main/kotlin/io/spring/deepdive/web/PostController.kt @@ -15,20 +15,25 @@ */ package io.spring.deepdive.web +import io.spring.deepdive.MarkdownConverter import io.spring.deepdive.repository.PostRepository -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* @RestController @RequestMapping("/api/post") -class PostApi(private val postRepository: PostRepository) { +class PostController(private val repository: PostRepository, private val markdownConverter: MarkdownConverter) { @GetMapping("/") - fun findAll() = postRepository.findAll() + fun findAll() = repository.findAll() @GetMapping("/{slug}") - fun findOne(@PathVariable slug: String) = postRepository.findOne(slug) + fun findOne(@PathVariable slug: String, @RequestParam converter: String?) = when (converter) { + "markdown" -> repository.findById(slug).map { it.copy( + title = markdownConverter.apply(it.title), + headline = markdownConverter.apply(it.headline), + content = markdownConverter.apply(it.content)) } + null -> repository.findById(slug) + else -> throw IllegalArgumentException("Only markdown converter is supported") + } } diff --git a/src/main/kotlin/io/spring/deepdive/web/PostDto.kt b/src/main/kotlin/io/spring/deepdive/web/PostDto.kt index 457d62a..490f0f5 100644 --- a/src/main/kotlin/io/spring/deepdive/web/PostDto.kt +++ b/src/main/kotlin/io/spring/deepdive/web/PostDto.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.spring.deepdive.web; +package io.spring.deepdive.web import io.spring.deepdive.MarkdownConverter import io.spring.deepdive.formatDate @@ -30,7 +30,7 @@ data class PostDto( fun Post.toDto(markdownConverter: MarkdownConverter) = PostDto( slug, - markdownConverter.apply(title), + title, markdownConverter.apply(headline), markdownConverter.apply(content), author, diff --git a/src/main/kotlin/io/spring/deepdive/web/UserApi.kt b/src/main/kotlin/io/spring/deepdive/web/UserController.kt similarity index 85% rename from src/main/kotlin/io/spring/deepdive/web/UserApi.kt rename to src/main/kotlin/io/spring/deepdive/web/UserController.kt index 7d4d832..9e23c6a 100644 --- a/src/main/kotlin/io/spring/deepdive/web/UserApi.kt +++ b/src/main/kotlin/io/spring/deepdive/web/UserController.kt @@ -24,12 +24,12 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/user") -class UserApi(private val userRepository: UserRepository) { +class UserController(private val repository: UserRepository) { @GetMapping("/") - fun findAll() = userRepository.findAll() + fun findAll() = repository.findAll() @GetMapping("/{login}") - fun findOne(@PathVariable login: String) = userRepository.findOne(login) + fun findOne(@PathVariable login: String) = repository.findById(login) } diff --git a/src/main/resources/templates/blog.mustache b/src/main/resources/templates/blog.mustache index d09e47b..3c40ae9 100644 --- a/src/main/resources/templates/blog.mustache +++ b/src/main/resources/templates/blog.mustache @@ -1,26 +1,19 @@ {{> header}} +

Recent Posts

+
-

Recent Posts

{{#posts}} -
-
- - -

{{title}}

- - -
- -
-

+

+
+

{{title}}

+ +
+
{{headline}} -

-
-
+
+
{{/posts}}
diff --git a/src/main/resources/templates/post.mustache b/src/main/resources/templates/post.mustache index ad29c47..e199886 100644 --- a/src/main/resources/templates/post.mustache +++ b/src/main/resources/templates/post.mustache @@ -2,22 +2,14 @@
-

{{post.title}}

- - +
-

- {{post.headline}} -

+ {{post.headline}} -

- {{post.content}} -

+ {{post.content}}
diff --git a/src/test/java/io/spring/deepdive/HtmlPagesTests.kt b/src/test/java/io/spring/deepdive/HtmlPagesTests.kt deleted file mode 100644 index 405f2f0..0000000 --- a/src/test/java/io/spring/deepdive/HtmlPagesTests.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.spring.deepdive; - -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.SpringBootTest.* -import org.springframework.boot.test.web.client.TestRestTemplate -import org.springframework.test.context.junit4.SpringRunner - -@RunWith(SpringRunner::class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -class HtmlPagesTests { - - @Autowired - private lateinit var restTemplate: TestRestTemplate - - @Test - fun `Assert content on blog page`() { - val body = restTemplate.getForObject("/", String::class.java) - assertThat(body) - .contains("Reactor Bismuth is out") - .contains("September 28th") - .contains("Sebastien") - .doesNotContain("brand-new generation") - } - - @Test - fun `Assert content on blog post page`() { - val body = restTemplate.getForObject("/spring-framework-5-0-goes-ga", String::class.java) - assertThat(body) - .contains("Spring Framework 5.0 goes GA") - .contains("Dear Spring community") - .contains("brand-new generation") - .contains("Juergen") - .doesNotContain("Sebastien") - } - -} diff --git a/src/test/java/io/spring/deepdive/PostApiTests.kt b/src/test/java/io/spring/deepdive/PostApiTests.kt deleted file mode 100644 index 249e8a7..0000000 --- a/src/test/java/io/spring/deepdive/PostApiTests.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2002-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 io.spring.deepdive - -import java.time.LocalDateTime - -import io.spring.deepdive.model.Post -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.context.SpringBootTest.* -import org.springframework.boot.test.web.client.TestRestTemplate -import org.springframework.core.ParameterizedTypeReference -import org.springframework.http.HttpMethod -import org.springframework.test.context.junit4.SpringRunner - -@RunWith(SpringRunner::class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -class PostApiTests { - - @Autowired - private lateinit var restTemplate: TestRestTemplate - - - @Test - fun `assert findAll JSON API is parsed correctly and contains 3 elements`() { - val posts = restTemplate.exchange("/api/post/", HttpMethod.GET, null, object: ParameterizedTypeReference>() {}).body - assertThat(posts).hasSize(3); - } - - @Test - fun `verify findOne JSON API`() { - val post = restTemplate.getForObject("/api/post/reactor-bismuth-is-out", Post::class.java) - assertThat(post.title).isEqualTo("Reactor Bismuth is out") - assertThat(post.headline).startsWith("It is my great pleasure to") - assertThat(post.content).startsWith("With the release of") - assertThat(post.addedAt).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 0)) - assertThat(post.author.firstname).isEqualTo("Simon") - } - -} diff --git a/src/test/kotlin/io/spring/deepdive/AbstractIntegrationTests.kt b/src/test/kotlin/io/spring/deepdive/AbstractIntegrationTests.kt new file mode 100644 index 0000000..b39d97e --- /dev/null +++ b/src/test/kotlin/io/spring/deepdive/AbstractIntegrationTests.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2002-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 io.spring.deepdive + +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTest.* +import org.springframework.boot.web.client.RestTemplateBuilder +import org.springframework.boot.web.server.LocalServerPort +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.web.client.RestTemplate + +@ExtendWith(SpringExtension::class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +abstract class AbstractIntegrationTests { + + @LocalServerPort + var port: Int? = null + + @Autowired + private lateinit var builder: RestTemplateBuilder + + protected lateinit var restTemplate: RestTemplate + + @BeforeAll + fun setup() { + // We don't use TestRestTemplate because of Spring Boot issues #10761 and #8062 + restTemplate = builder.rootUri("http://localhost:$port").build() + } + +} diff --git a/src/test/java/io/spring/deepdive/FunnyTests.kt b/src/test/kotlin/io/spring/deepdive/FunnyTests.kt similarity index 95% rename from src/test/java/io/spring/deepdive/FunnyTests.kt rename to src/test/kotlin/io/spring/deepdive/FunnyTests.kt index ed26f44..b9d9a46 100644 --- a/src/test/java/io/spring/deepdive/FunnyTests.kt +++ b/src/test/kotlin/io/spring/deepdive/FunnyTests.kt @@ -15,7 +15,7 @@ */ package io.spring.deepdive -import org.junit.Test +import org.junit.jupiter.api.Test class FunnyTests { diff --git a/src/test/kotlin/io/spring/deepdive/HtmlTests.kt b/src/test/kotlin/io/spring/deepdive/HtmlTests.kt new file mode 100644 index 0000000..f07a1da --- /dev/null +++ b/src/test/kotlin/io/spring/deepdive/HtmlTests.kt @@ -0,0 +1,31 @@ +package io.spring.deepdive + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +import org.springframework.web.client.getForObject + +class HtmlTests : AbstractIntegrationTests() { + + @Test + fun `Assert content on blog page`() { + val body = restTemplate.getForObject("/") + assertThat(body) + .contains("Reactor Bismuth is out") + .contains("September 28th") + .contains("Sebastien") + .doesNotContain("brand-new generation") + } + + @Test + fun `Assert content on blog post page`() { + val body = restTemplate.getForObject("/spring-framework-5-0-goes-ga") + assertThat(body) + .contains("Spring Framework 5.0 goes GA") + .contains("Dear Spring community") + .contains("brand-new generation") + .contains("Juergen") + .doesNotContain("Sebastien") + } + +} diff --git a/src/test/kotlin/io/spring/deepdive/PostJsonApiTests.kt b/src/test/kotlin/io/spring/deepdive/PostJsonApiTests.kt new file mode 100644 index 0000000..0c43212 --- /dev/null +++ b/src/test/kotlin/io/spring/deepdive/PostJsonApiTests.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2002-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 io.spring.deepdive + +import java.time.LocalDateTime + +import io.spring.deepdive.model.Post +import org.assertj.core.api.Assertions.* +import org.junit.jupiter.api.Test +import org.springframework.web.client.HttpServerErrorException + +import org.springframework.web.client.getForObject + +class PostJsonApiTests : AbstractIntegrationTests() { + + @Test + fun `Assert findAll JSON API is parsed correctly and contains 3 elements`() { + val posts = restTemplate.getForObject>("/api/post/") + assertThat(posts).hasSize(3) + } + + @Test + fun `Verify findOne JSON API`() { + val post = restTemplate.getForObject("/api/post/reactor-bismuth-is-out")!! + assertThat(post.title).isEqualTo("Reactor Bismuth is out") + assertThat(post.headline).startsWith("It is my great pleasure to") + assertThat(post.content).startsWith("With the release of") + assertThat(post.addedAt).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 0)) + assertThat(post.author.firstname).isEqualTo("Simon") + } + + @Test + fun `Verify findOne JSON API with Markdown converter`() { + val post = this.restTemplate.getForObject("/api/post/reactor-bismuth-is-out?converter=markdown", Post::class.java)!! + assertThat(post.title).startsWith("

Reactor Bismuth is out") + assertThat(post.headline).doesNotContain("**3.1.0.RELEASE**").contains("3.1.0.RELEASE") + assertThat(post.content).doesNotContain("[Spring Framework 5.0](https://spring.io/blog/2017/09/28/spring-framework-5-0-goes-ga)").contains("") + assertThat(post.addedAt).isEqualTo(LocalDateTime.of(2017, 9, 28, 12, 0)) + assertThat(post.author.firstname).isEqualTo("Simon") + } + + @Test + fun `Verify findOne JSON API with invalid converter`() { + assertThatThrownBy { this.restTemplate.getForEntity("/api/post/reactor-bismuth-is-out?converter=foo", Post::class.java) }.isInstanceOf(HttpServerErrorException::class.java) + } + +} diff --git a/src/test/java/io/spring/deepdive/UserApiTests.kt b/src/test/kotlin/io/spring/deepdive/UserJsonApiTests.kt similarity index 56% rename from src/test/java/io/spring/deepdive/UserApiTests.kt rename to src/test/kotlin/io/spring/deepdive/UserJsonApiTests.kt index 1892ce0..cfa65df 100644 --- a/src/test/java/io/spring/deepdive/UserApiTests.kt +++ b/src/test/kotlin/io/spring/deepdive/UserJsonApiTests.kt @@ -16,35 +16,22 @@ package io.spring.deepdive import io.spring.deepdive.model.User -import org.junit.Test -import org.junit.runner.RunWith - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.client.TestRestTemplate -import org.springframework.core.ParameterizedTypeReference -import org.springframework.http.HttpMethod -import org.springframework.test.context.junit4.SpringRunner import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.web.client.getForObject -@RunWith(SpringRunner::class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class UserApiTests { - - @Autowired - private lateinit var restTemplate: TestRestTemplate - +class UserJsonApiTests : AbstractIntegrationTests() { @Test fun `Assert FindAll JSON API is parsed correctly and contains 11 elements`() { - val users = restTemplate.exchange("/api/user/", HttpMethod.GET, null, object: ParameterizedTypeReference>() {}).body + val users = restTemplate.getForObject>("/api/user/") assertThat(users).hasSize(11) } @Test - fun `verify findOne JSON API`() { - val user = this.restTemplate.getForObject("/api/user/MkHeck", User::class.java) + fun `Verify findOne JSON API`() { + val user = restTemplate.getForObject("/api/user/MkHeck")!! assertThat(user.login).isEqualTo("MkHeck") assertThat(user.firstname).isEqualTo("Mark") assertThat(user.lastname).isEqualTo("Heckler") diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..e6d55f8 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testinstance.lifecycle.default = per_class \ No newline at end of file