Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Building web applications with Spring Boot and Kotlin :: Learn how to easily build and test web applications with Spring, Kotlin, Junit 5 and JPA

NotificationsYou must be signed in to change notification settings

spring-guides/tut-spring-boot-kotlin

This tutorial shows you how to build efficiently a sample blog application by combining the power ofSpring Boot andKotlin.

If you are starting with Kotlin, you can learn the language by reading thereference documentation, following the onlineKotlin Koans tutorial or just usingSpring Framework reference documentation which now provides code samples in Kotlin.

Spring Kotlin support is documented in theSpring Framework andSpring Boot reference documentation. If you need help, search or ask questions with thespring andkotlin tags on StackOverflow or come discuss in the#spring channel ofKotlin Slack.

Creating a New Project

First we need to create a Spring Boot application, which can be done in a number of ways.

Using the Initializr Website

Visithttps://start.spring.io and choose the Kotlin language.Gradle is the most commonly used build tool in Kotlin, and it provides a Kotlin DSL which is used by default when generating a Kotlin project, so this is the recommended choice. But you can also use Maven if you are more comfortable with it.Notice that you can usehttps://start.spring.io/#!language=kotlin&type=gradle-project-kotlin to have Kotlin and Gradle selected by default.

  1. Select "Gradle - Kotlin" or "Maven" depending on which build tool you want to use

  2. Enter the following artifact coordinates:blog

  3. Add the following dependencies:

    • Spring Web

    • Mustache

    • Spring Data JPA

    • H2 Database

    • Spring Boot DevTools

  4. Click "Generate Project".

The .zip file contains a standard project in the root directory, so you might want to create an empty directory before you unpack it.

Using command line

You can use the Initializr HTTP APIfrom the command line with, for example, curl on a UN*X like system:

$ mkdir blog && cd blog$ curl https://start.spring.io/starter.zip -d language=kotlin -d type=gradle-project-kotlin -d dependencies=web,mustache,jpa,h2,devtools -d packageName=com.example.blog -d name=Blog -o blog.zip

Add-d type=gradle-project if you want to use Gradle.

Using IntelliJ IDEA

Spring Initializr is also integrated in IntelliJ IDEA Ultimate edition and allows you to create and import a new project without having to leave the IDE for the command-line or the web UI.

To access the wizard, go to File | New | Project, and select Spring Initializr.

Follow the steps of the wizard to use the following parameters:

  • Artifact: "blog"

  • Type: "Gradle - Kotlin" or "Maven"

  • Language: Kotlin

  • Name: "Blog"

  • Dependencies: "Spring Web Starter", "Mustache", "Spring Data JPA", "H2 Database" and "Spring Boot DevTools"

Understanding the Gradle Build

If you’re using a Maven Build, you canskip to the dedicated section.

Plugins

In addition to the obviousKotlin Gradle plugin, the default configuration declares thekotlin-spring plugin which automatically opens classes and methods (unlike in Java, the default qualifier isfinal in Kotlin) annotated or meta-annotated with Spring annotations. This is useful to be able to create@Configuration or@Transactional beans without having to add theopen qualifier required by CGLIB proxies for example.

In order to be able to use Kotlin non-nullable properties with JPA,Kotlin JPA plugin is also enabled. It generates no-arg constructors for any class annotated with@Entity,@MappedSuperclass or@Embeddable.

build.gradle.kts

importorg.jetbrains.kotlin.gradle.tasks.KotlinCompileplugins {  id("org.springframework.boot") version"3.2.2"  id("io.spring.dependency-management") version"1.1.4"  kotlin("jvm") version"1.9.22"  kotlin("plugin.spring") version"1.9.22"  kotlin("plugin.jpa") version"1.9.22"}

Compiler options

One of Kotlin’s key features isnull-safety - which cleanly deals withnull values at compile time rather than bumping into the famousNullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or no value" semantics without paying the cost of wrappers likeOptional. Note that Kotlin allows using functional constructs with nullable values; check out thiscomprehensive guide to Kotlin null-safety.

Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null-safety of the whole Spring Framework API via tooling-friendly annotations declared in theorg.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized asplatform types for which null-checks are relaxed.Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers, with the advantage of dealing withnull related issues at compile time.

This feature can be enabled by adding the-Xjsr305 compiler flag with thestrict options.

build.gradle.kts

tasks.withType<KotlinCompile> {  kotlinOptions {    freeCompilerArgs+="-Xjsr305=strict"  }}

Dependencies

2 Kotlin specific libraries are required (the standard library is added automatically with Gradle) for such Spring Boot web application and configured by default:

  • kotlin-reflect is Kotlin reflection library

  • jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes (single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported)

build.gradle.kts

dependencies {  implementation("org.springframework.boot:spring-boot-starter-data-jpa")  implementation("org.springframework.boot:spring-boot-starter-mustache")  implementation("org.springframework.boot:spring-boot-starter-web")  implementation("com.fasterxml.jackson.module:jackson-module-kotlin")  implementation("org.jetbrains.kotlin:kotlin-reflect")  runtimeOnly("com.h2database:h2")  runtimeOnly("org.springframework.boot:spring-boot-devtools")  testImplementation("org.springframework.boot:spring-boot-starter-test")}

Recent versions of H2 require special configuration to properly escape reserved keywords likeuser.

src/main/resources/application.properties

spring.jpa.properties.hibernate.globally_quoted_identifiers=truespring.jpa.properties.hibernate.globally_quoted_identifiers_skip_column_definitions=true

Spring Boot Gradle plugin automatically uses the Kotlin version declared via the Kotlin Gradle plugin.

Understanding the Maven Build

Plugins

In addition to the obviousKotlin Maven plugin, the default configuration declares thekotlin-spring plugin which automatically opens classes and methods (unlike in Java, the default qualifier isfinal in Kotlin) annotated or meta-annotated with Spring annotations. This is useful to be able to create@Configuration or@Transactional beans without having to add theopen qualifier required by CGLIB proxies for example.

In order to be able to use Kotlin non-nullable properties with JPA,Kotlin JPA plugin is also enabled. It generates no-arg constructors for any class annotated with@Entity,@MappedSuperclass or@Embeddable.

pom.xml

<build>    <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>    <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>    <plugins>      <plugin>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-maven-plugin</artifactId>      </plugin>      <plugin>        <groupId>org.jetbrains.kotlin</groupId>        <artifactId>kotlin-maven-plugin</artifactId>        <configuration>          <compilerPlugins>            <plugin>jpa</plugin>            <plugin>spring</plugin>          </compilerPlugins>          <args>            <arg>-Xjsr305=strict</arg>          </args>        </configuration>        <dependencies>          <dependency>            <groupId>org.jetbrains.kotlin</groupId>            <artifactId>kotlin-maven-noarg</artifactId>            <version>${kotlin.version}</version>          </dependency>          <dependency>            <groupId>org.jetbrains.kotlin</groupId>            <artifactId>kotlin-maven-allopen</artifactId>            <version>${kotlin.version}</version>          </dependency>        </dependencies>      </plugin>    </plugins>  </build>

One of Kotlin’s key features isnull-safety - which cleanly deals withnull values at compile time rather than bumping into the famousNullPointerException at runtime. This makes applications safer through nullability declarations and expressing "value or no value" semantics without paying the cost of wrappers likeOptional. Note that Kotlin allows using functional constructs with nullable values; check out thiscomprehensive guide to Kotlin null-safety.

Although Java does not allow one to express null-safety in its type-system, Spring Framework provides null-safety of the whole Spring Framework API via tooling-friendly annotations declared in theorg.springframework.lang package. By default, types from Java APIs used in Kotlin are recognized asplatform types for which null-checks are relaxed.Kotlin support for JSR 305 annotations + Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers, with the advantage of dealing withnull related issues at compile time.

This feature can be enabled by adding the-Xjsr305 compiler flag with thestrict options.

Notice also that Kotlin compiler is configured to generate Java 8 bytecode (Java 6 by default).

Dependencies

3 Kotlin specific libraries are required for such Spring Boot web application and configured by default:

  • kotlin-stdlib is the Kotlin standard library

  • kotlin-reflect is Kotlin reflection library

  • jackson-module-kotlin adds support for serialization/deserialization of Kotlin classes and data classes (single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported)

pom.xml

<dependencies>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-jpa</artifactId>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-mustache</artifactId>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <dependency>    <groupId>com.fasterxml.jackson.module</groupId>    <artifactId>jackson-module-kotlin</artifactId>  </dependency>  <dependency>    <groupId>org.jetbrains.kotlin</groupId>    <artifactId>kotlin-reflect</artifactId>  </dependency>  <dependency>    <groupId>org.jetbrains.kotlin</groupId>    <artifactId>kotlin-stdlib</artifactId>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-devtools</artifactId>    <scope>runtime</scope>  </dependency>  <dependency>    <groupId>com.h2database</groupId>    <artifactId>h2</artifactId>    <scope>runtime</scope>  </dependency>  <dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-test</artifactId>    <scope>test</scope>  </dependency></dependencies>

Understanding the generated Application

src/main/kotlin/com/example/blog/BlogApplication.kt

packagecom.example.blogimportorg.springframework.boot.autoconfigure.SpringBootApplicationimportorg.springframework.boot.runApplication@SpringBootApplicationclassBlogApplicationfunmain(args:Array<String>) {  runApplication<BlogApplication>(*args)}

Compared to Java, you can notice the lack of semicolons, the lack of brackets on empty class (you can add some if you need to declare beans via@Bean annotation) and the use ofrunApplication top level function.runApplication<BlogApplication>(*args) is Kotlin idiomatic alternative toSpringApplication.run(BlogApplication::class.java, *args) and can be used to customize the application with following syntax.

src/main/kotlin/com/example/blog/BlogApplication.kt

funmain(args:Array<String>) {  runApplication<BlogApplication>(*args) {    setBannerMode(Banner.Mode.OFF)  }}

Writing your first Kotlin controller

Let’s create a simple controller to display a simple web page.

src/main/kotlin/com/example/blog/HtmlController.kt

packagecom.example.blogimportorg.springframework.stereotype.Controllerimportorg.springframework.ui.Modelimportorg.springframework.ui.setimportorg.springframework.web.bind.annotation.GetMapping@ControllerclassHtmlController {  @GetMapping("/")funblog(model:Model):String {    model["title"]="Blog"return"blog"  }}

Notice that we are using here aKotlin extension that allows to add Kotlin functions or operators to existing Spring types. Here we import theorg.springframework.ui.set extension function in order to be able to writemodel["title"] = "Blog" instead ofmodel.addAttribute("title", "Blog").TheSpring Framework KDoc API lists all the Kotlin extensions provided to enrich the Java API.

We also need to create the associated Mustache templates.

src/main/resources/templates/header.mustache

<html><head>  <title>{{title}}</title></head><body>

src/main/resources/templates/footer.mustache

</body></html>

src/main/resources/templates/blog.mustache

{{> header}}<h1>{{title}}</h1>{{> footer}}

Start the web application by running themain function ofBlogApplication.kt, and go tohttp://localhost:8080/, you should see a sober web page with a "Blog" headline.

Testing with JUnit 5

JUnit 5 now used by default in Spring Boot provides various features very handy with Kotlin, includingautowiring of constructor/method parameters which allows to use non-nullableval properties and the possibility to use@BeforeAll/@AfterAll on regular non-static methods.

Writing JUnit 5 tests in Kotlin

For the sake of this example, let’s create an integration test in order to demonstrate various features:

  • We use real sentences between backticks instead of camel-case to provide expressive test function names

  • JUnit 5 allows to inject constructor and method parameters, which is a good fit with Kotlin read-only and non-nullable properties

  • This code leveragesgetForObject andgetForEntity Kotlin extensions (you need to import them)

src/test/kotlin/com/example/blog/IntegrationTests.kt

@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)classIntegrationTests(@AutowiredvalrestTemplate:TestRestTemplate) {  @Testfun`Assert blog page title, content and status code`() {val entity= restTemplate.getForEntity<String>("/")    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)    assertThat(entity.body).contains("<h1>Blog</h1>")  }}

Test instance lifecycle

Sometimes you need to execute a method before or after all tests of a given class. Like Junit 4, JUnit 5 requires by default these methods to be static (which translates tocompanion object in Kotlin, which is quite verbose and not straightforward) because test classes are instantiated one time per test.

But Junit 5 allows you to change this default behavior and instantiate test classes one time per class. This can be done invarious ways, here we will use a property file to change the default behavior for the whole project:

src/test/resources/junit-platform.properties

junit.jupiter.testinstance.lifecycle.default = per_class

With this configuration, we can now use@BeforeAll and@AfterAll annotations on regular methods like shown in updated version ofIntegrationTests above.

src/test/kotlin/com/example/blog/IntegrationTests.kt

@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)classIntegrationTests(@AutowiredvalrestTemplate:TestRestTemplate) {  @BeforeAllfunsetup() {println(">> Setup")  }  @Testfun`Assert blog page title, content and status code`() {println(">> Assert blog page title, content and status code")val entity= restTemplate.getForEntity<String>("/")    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)    assertThat(entity.body).contains("<h1>Blog</h1>")  }  @Testfun`Assert article page title, content and status code`() {println(">> TODO")  }  @AfterAllfunteardown() {println(">> Tear down")  }}

Creating your own extensions

Instead of using util classes with abstract methods like in Java, it is usual in Kotlin to provide such functionalities via Kotlin extensions. Here we are going to add aformat() function to the existingLocalDateTime type in order to generate text with the English date format.

src/main/kotlin/com/example/blog/Extensions.kt

fun LocalDateTime.format():String=this.format(englishDateFormatter)privateval daysLookup= (1..31).associate { it.toLong() to getOrdinal(it) }privateval englishDateFormatter=DateTimeFormatterBuilder()    .appendPattern("yyyy-MM-dd")    .appendLiteral("")    .appendText(ChronoField.DAY_OF_MONTH, daysLookup)    .appendLiteral("")    .appendPattern("yyyy")    .toFormatter(Locale.ENGLISH)privatefungetOrdinal(n:Int)=when {  nin11..13->"${n}th"  n%10==1->"${n}st"  n%10==2->"${n}nd"  n%10==3->"${n}rd"else->"${n}th"}fun String.toSlug()= lowercase(Locale.getDefault())    .replace("\n","")    .replace("[^a-z\\d\\s]".toRegex(),"")    .split("")    .joinToString("-")    .replace("-+".toRegex(),"-")

We will leverage these extensions in the next section.

Persistence with JPA

In order to make lazy fetching working as expected, entities should beopen as described inKT-28525. We are going to use the Kotlinallopen plugin for that purpose.

With Gradle:

build.gradle.kts

plugins {...  kotlin("plugin.allopen") version"1.9.22"}allOpen {  annotation("jakarta.persistence.Entity")  annotation("jakarta.persistence.Embeddable")  annotation("jakarta.persistence.MappedSuperclass")}

Or with Maven:

pom.xml

<plugin>  <artifactId>kotlin-maven-plugin</artifactId>  <groupId>org.jetbrains.kotlin</groupId>  <configuration>    ...    <compilerPlugins>      ...      <plugin>all-open</plugin>    </compilerPlugins>    <pluginOptions>      <option>all-open:annotation=jakarta.persistence.Entity</option>      <option>all-open:annotation=jakarta.persistence.Embeddable</option>      <option>all-open:annotation=jakarta.persistence.MappedSuperclass</option>    </pluginOptions>  </configuration></plugin>

Then we create our model by using Kotlinprimary constructor concise syntax which allows to declare at the same time the properties and the constructor parameters.

src/main/kotlin/com/example/blog/Entities.kt

@EntityclassArticle(vartitle:String,varheadline:String,varcontent:String,    @ManyToOnevarauthor:User,varslug:String = title.toSlug(),varaddedAt:LocalDateTime =LocalDateTime.now(),    @Id @GeneratedValuevarid:Long? =null)@EntityclassUser(varlogin:String,varfirstname:String,varlastname:String,vardescription:String? =null,    @Id @GeneratedValuevarid:Long? =null)

Notice that we are using here ourString.toSlug() extension to provide a default argument to theslug parameter ofArticle constructor. Optional parameters with default values are defined at the last position in order to make it possible to omit them when using positional arguments (Kotlin also supportsnamed arguments). Notice that in Kotlin it is not unusual to group concise class declarations in the same file.

Note
Here we don’t usedata classes withval properties because JPA is not designed to work with immutable classes or the methods generated automatically bydata classes. If you are using other Spring Data flavor, most of them are designed to support such constructs so you should use classes likedata class User(val login: String, …​) when using Spring Data MongoDB, Spring Data JDBC, etc.
Note
While Spring Data JPA makes it possible to use natural IDs (it could have been thelogin property inUser class) viaPersistable, it is not a good fit with Kotlin due toKT-6653, that’s why it is recommended to always use entities with generated IDs in Kotlin.

We also declare our Spring Data JPA repositories as following.

src/main/kotlin/com/example/blog/Repositories.kt

interfaceArticleRepository :CrudRepository<Article,Long> {funfindBySlug(slug:String):Article?funfindAllByOrderByAddedAtDesc():Iterable<Article>}interfaceUserRepository :CrudRepository<User,Long> {funfindByLogin(login:String):User?}

And we write JPA tests to check whether basic use cases work as expected.

src/test/kotlin/com/example/blog/RepositoriesTests.kt

@DataJpaTestclassRepositoriesTests @Autowired constructor(valentityManager:TestEntityManager,valuserRepository:UserRepository,valarticleRepository:ArticleRepository) {  @Testfun`When findByIdOrNull then return Article`() {val johnDoe=User("johnDoe","John","Doe")    entityManager.persist(johnDoe)val article=Article("Lorem","Lorem","dolor sit amet", johnDoe)    entityManager.persist(article)    entityManager.flush()val found= articleRepository.findByIdOrNull(article.id!!)    assertThat(found).isEqualTo(article)  }  @Testfun`When findByLogin then return User`() {val johnDoe=User("johnDoe","John","Doe")    entityManager.persist(johnDoe)    entityManager.flush()val user= userRepository.findByLogin(johnDoe.login)    assertThat(user).isEqualTo(johnDoe)  }}
Note
We use here theCrudRepository.findByIdOrNull Kotlin extension provided by default with Spring Data, which is a nullable variant of theOptional basedCrudRepository.findById. Read the greatNull is your friend, not a mistake blog post for more details.

Implementing the blog engine

We update the "blog" Mustache templates.

src/main/resources/templates/blog.mustache

{{> header}}<h1>{{title}}</h1><div>  {{#articles}}    <section>      <header>        <h2><a href="/article/{{slug}}">{{title}}</a></h2>        <div>By  <strong>{{author.firstname}}</strong>, on <strong>{{addedAt}}</strong></div>      </header>      <div>        {{headline}}      </div>    </section>  {{/articles}}</div>{{> footer}}

And we create an "article" new one.

src/main/resources/templates/article.mustache

{{> header}}<section>  <header>    <h1>{{article.title}}</h1>    <p>By  <strong>{{article.author.firstname}}</strong>, on <strong>{{article.addedAt}}</strong></p>  </header>  <div>    {{article.headline}}    {{article.content}}  </div></section>{{> footer}}

We update theHtmlController in order to render blog and article pages with the formatted date.ArticleRepository constructor parameter will be automatically autowired sinceHtmlController has a single constructor (implicit@Autowired).

src/main/kotlin/com/example/blog/HtmlController.kt

@ControllerclassHtmlController(privatevalrepository:ArticleRepository) {  @GetMapping("/")funblog(model:Model):String {    model["title"]="Blog"    model["articles"]= repository.findAllByOrderByAddedAtDesc().map { it.render() }return"blog"  }  @GetMapping("/article/{slug}")funarticle(@PathVariableslug:String,model:Model):String {val article= repository        .findBySlug(slug)        ?.render()?:throwResponseStatusException(HttpStatus.NOT_FOUND,"This article does not exist")    model["title"]= article.title    model["article"]= articlereturn"article"  }fun Article.render()=RenderedArticle(      slug,      title,      headline,      content,      author,      addedAt.format()  )data classRenderedArticle(valslug:String,valtitle:String,valheadline:String,valcontent:String,valauthor:User,valaddedAt:String)}

Then, we add data initialization to a newBlogConfiguration class.

src/main/kotlin/com/example/blog/BlogConfiguration.kt

@ConfigurationclassBlogConfiguration {  @BeanfundatabaseInitializer(userRepository:UserRepository,articleRepository:ArticleRepository)=ApplicationRunner {val johnDoe= userRepository.save(User("johnDoe","John","Doe"))    articleRepository.save(Article(        title="Lorem",        headline="Lorem",        content="dolor sit amet",        author= johnDoe    ))    articleRepository.save(Article(        title="Ipsum",        headline="Ipsum",        content="dolor sit amet",        author= johnDoe    ))  }}
Note
Notice the usage of named parameters to make the code more readable.

And we also update the integration tests accordingly.

src/test/kotlin/com/example/blog/IntegrationTests.kt

@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)classIntegrationTests(@AutowiredvalrestTemplate:TestRestTemplate) {  @BeforeAllfunsetup() {println(">> Setup")  }  @Testfun`Assert blog page title, content and status code`() {println(">> Assert blog page title, content and status code")val entity= restTemplate.getForEntity<String>("/")    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)    assertThat(entity.body).contains("<h1>Blog</h1>","Lorem")  }  @Testfun`Assert article page title, content and status code`() {println(">> Assert article page title, content and status code")val title="Lorem"val entity= restTemplate.getForEntity<String>("/article/${title.toSlug()}")    assertThat(entity.statusCode).isEqualTo(HttpStatus.OK)    assertThat(entity.body).contains(title,"Lorem","dolor sit amet")  }  @AfterAllfunteardown() {println(">> Tear down")  }}

Start (or restart) the web application, and go tohttp://localhost:8080/, you should see the list of articles with clickable links to see a specific article.

Exposing HTTP API

We are now going to implement the HTTP API via@RestController annotated controllers.

src/main/kotlin/com/example/blog/HttpControllers.kt

@RestController@RequestMapping("/api/article")classArticleController(privatevalrepository:ArticleRepository) {  @GetMapping("/")funfindAll()= repository.findAllByOrderByAddedAtDesc()  @GetMapping("/{slug}")funfindOne(@PathVariableslug:String)=      repository.findBySlug(slug)?:throwResponseStatusException(HttpStatus.NOT_FOUND,"This article does not exist")}@RestController@RequestMapping("/api/user")classUserController(privatevalrepository:UserRepository) {  @GetMapping("/")funfindAll()= repository.findAll()  @GetMapping("/{login}")funfindOne(@PathVariablelogin:String)=      repository.findByLogin(login)?:throwResponseStatusException(HttpStatus.NOT_FOUND,"This user does not exist")}

For tests, instead of integration tests, we are going to leverage@WebMvcTest andMockk which is similar toMockito but better suited for Kotlin.

Since@MockBean and@SpyBean annotations are specific to Mockito, we are going to leverageSpringMockK which provides similar@MockkBean and@SpykBean annotations for Mockk.

With Gradle:

build.gradle.kts

testImplementation("org.springframework.boot:spring-boot-starter-test") {  exclude(module="mockito-core")}testImplementation("org.junit.jupiter:junit-jupiter-api")testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")testImplementation("com.ninja-squad:springmockk:4.0.2")

Or with Maven:

pom.xml

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-test</artifactId>  <scope>test</scope></dependency><dependency>  <groupId>org.junit.jupiter</groupId>  <artifactId>junit-jupiter-engine</artifactId>  <scope>test</scope></dependency><dependency>  <groupId>com.ninja-squad</groupId>  <artifactId>springmockk</artifactId>  <version>4.0.2</version>  <scope>test</scope></dependency>

src/test/kotlin/com/example/blog/HttpControllersTests.kt

@WebMvcTestclassHttpControllersTests(@AutowiredvalmockMvc:MockMvc) {  @MockkBeanlateinitvar userRepository:UserRepository  @MockkBeanlateinitvar articleRepository:ArticleRepository  @Testfun`List articles`() {val johnDoe=User("johnDoe","John","Doe")val lorem5Article=Article("Lorem","Lorem","dolor sit amet", johnDoe)val ipsumArticle=Article("Ipsum","Ipsum","dolor sit amet", johnDoe)    every { articleRepository.findAllByOrderByAddedAtDesc() } returnslistOf(lorem5Article, ipsumArticle)    mockMvc.perform(get("/api/article/").accept(MediaType.APPLICATION_JSON))        .andExpect(status().isOk)        .andExpect(content().contentType(MediaType.APPLICATION_JSON))        .andExpect(jsonPath("\$.[0].author.login").value(johnDoe.login))        .andExpect(jsonPath("\$.[0].slug").value(lorem5Article.slug))        .andExpect(jsonPath("\$.[1].author.login").value(johnDoe.login))        .andExpect(jsonPath("\$.[1].slug").value(ipsumArticle.slug))  }  @Testfun`List users`() {val johnDoe=User("johnDoe","John","Doe")val janeDoe=User("janeDoe","Jane","Doe")    every { userRepository.findAll() } returnslistOf(johnDoe, janeDoe)    mockMvc.perform(get("/api/user/").accept(MediaType.APPLICATION_JSON))        .andExpect(status().isOk)        .andExpect(content().contentType(MediaType.APPLICATION_JSON))        .andExpect(jsonPath("\$.[0].login").value(johnDoe.login))        .andExpect(jsonPath("\$.[1].login").value(janeDoe.login))  }}
Note
$ needs to be escaped in strings as it is used for string interpolation.

Configuration properties

In Kotlin, the recommended way to manage your application properties is to use read-only properties.

src/main/kotlin/com/example/blog/BlogProperties.kt

@ConfigurationProperties("blog")data classBlogProperties(vartitle:String,valbanner:Banner) {data classBanner(valtitle:String? =null,valcontent:String)}

Then we enable it atBlogApplication level.

src/main/kotlin/com/example/blog/BlogApplication.kt

@SpringBootApplication@EnableConfigurationProperties(BlogProperties::class)classBlogApplication {// ...}

To generateyour own metadata in order to get these custom properties recognized by your IDE,kapt should be configured with thespring-boot-configuration-processor dependency as following.

build.gradle.kts

plugins {...  kotlin("kapt") version"1.9.22"}dependencies {...  kapt("org.springframework.boot:spring-boot-configuration-processor")}
Note
Note that some features (such as detecting the default value or deprecated items) are not working due to limitations in the model kapt provides. Also annotation processing is not yet supported with Maven due toKT-18022, seeinitializr#438 for more details.

In IntelliJ IDEA:

  • Make sure Spring Boot plugin in enabled in menu File | Settings | Plugins | Spring Boot

  • Enable annotation processing via menu File | Settings | Build, Execution, Deployment | Compiler | Annotation Processors | Enable annotation processing

  • SinceKapt is not yet integrated in IDEA, you need to run manually the command./gradlew kaptKotlin to generate the metadata

Your custom properties should now be recognized when editingapplication.properties (autocomplete, validation, etc.).

src/main/resources/application.properties

blog.title=Blogblog.banner.title=Warningblog.banner.content=The blog will be down tomorrow.

Edit the template and the controller accordingly.

src/main/resources/templates/blog.mustache

{{> header}}<div>  {{#banner.title}}  <section>    <header>      <h2>{{banner.title}}</h2>    </header>    <div>      {{banner.content}}    </div>  </section>  {{/banner.title}}  ...</div>{{> footer}}

src/main/kotlin/com/example/blog/HtmlController.kt

@ControllerclassHtmlController(privatevalrepository:ArticleRepository,privatevalproperties:BlogProperties) {  @GetMapping("/")funblog(model:Model):String {    model["title"]= properties.title    model["banner"]= properties.banner    model["articles"]= repository.findAllByOrderByAddedAtDesc().map { it.render() }return"blog"  }// ...

Restart the web application, refreshhttp://localhost:8080/, you should see the banner on the blog homepage.

Conclusion

We have now finished to build this sample Kotlin blog application. The source codeis available on Github. You can also have a look toSpring Framework andSpring Boot reference documentation if you need more details on specific features.

About

Building web applications with Spring Boot and Kotlin :: Learn how to easily build and test web applications with Spring, Kotlin, Junit 5 and JPA

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors22


[8]ページ先頭

©2009-2025 Movatter.jp