Http API 생성하기
article 목록 조회와 article을 slug로조회하는 컨트롤러와
user 목록 조회와 user을 login으로 조회하는 컨트롤러를 생성한다
src/main/kotlin/com/example/demo/HttpControllers.kt
package com.example.demo.blog
import org.springframework.http.HttpStatus
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.server.ResponseStatusException
@RestController
@RequestMapping("/api/articles")
class ArticleController (private val repositoriy: ArticleRepositoriy) {
@GetMapping("/")
fun findAll() = repositoriy.findAllByOrderByAddedAtDesc()
@GetMapping("/{slug}")
fun findOne(@PathVariable slug : String) =
repositoriy.findBySlug(slug)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "this article does not exist")
}
@RestController
@RequestMapping("/api/users")
class UserController(private val repositoriy: UserRepository){
@GetMapping("/")
fun findAll() = repositoriy.findAll();
@GetMapping("/{login}")
fun findOne(@PathVariable login: String) =
repositoriy.findByLogin(login)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "this user does not exist")
}
Http API 테스트하기
테스트를 하기 위해 @WebMvcTest와 Mockk를 사용할건데, Mockk는 Mockito와 비슷하지만 코틀린에 더 최적화되어있다
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")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
testImplementation("org.springframework.boot:spring-boot-starter-test"){
exclude(module="junit")
exclude(module="mockito-core")
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
implementation("com.ninja-squad:springmockk:3.0.1")
}
기존 mockito를 제외하고 mockk를 추가해준다
src/test/kotlin/com/example/demo/HttpControllersTests.kt
package com.example.demo
import com.example.demo.blog.Article
import com.example.demo.blog.ArticleRepositoriy
import com.example.demo.blog.User
import com.example.demo.blog.UserRepository
import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
@WebMvcTest
class HttpsControllersTests(@Autowired val mockMvc: MockMvc) {
@MockkBean
private lateinit var articleRepositoriy: ArticleRepositoriy
@MockkBean
private lateinit var userRepository: UserRepository
@Test
fun `List articles`() {
val user = User("login", "firstName", "lastName")
val article1 = Article("title1", "headline1", "content1", user)
val article2 = Article("title2", "headline2", "content2", user)
every { articleRepositoriy.findAllByOrderByAddedAtDesc() } returns
listOf(article1, article2)
mockMvc.perform(get("/api/articles/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("\$.[0].author.login").value(user.login))
.andExpect(jsonPath("\$.[0].slug").value(article1.slug))
.andExpect(jsonPath("\$.[1].author.login").value(article2.author.login))
.andExpect(jsonPath("\$.[1].slug").value(article2.slug))
}
@Test
fun `List users`() {
val user1 = User("login1", "firstName1", "lastName1")
val user2 = User("login2", "firstName2", "lastName2")
every { userRepository.findAll() } returns listOf(user1, user2)
mockMvc.perform(get("/api/users/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk)
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("\$.[0].login").value(user1.login))
.andExpect(jsonPath("\$.[1].login").value(user2.login))
}
}
List articles 테스트를 보면
every 함수를 통해 articleRepositoriy.findAllByOrderByAddedAtDesc()의 리턴 목록을 지정해주고, mockMvc를 이용해/api/articles/
API가 제대로 작동하는지 검증한다.
프로퍼티 설정하기
build.gradle.kts
plugins {
...
kotlin("kapt") version "1.4.32"
}
dependencies {
...
kapt("org.springframework.boot:spring-boot-configuration-processor")
}
코틀린에서는 @ConstructorBinding와 @ConfigurationProperties를 이용해 프로퍼티를 관리하는 것을 추천한다
src/main/kotlin/com/example/demo/BlogProperties.kt
package com.example.demo
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.ConstructorBinding
@ConstructorBinding
@ConfigurationProperties("blog")
data class BlogProperties(var title: String, val banner: Banner) {
data class Banner(val title: String?=null, val content:String)
}
BlogApplication 레벨에서 프로퍼티를 허용해준다
src/main/kotlin/com/example/blog/BlogApplication.kt
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
@SpringBootApplication
@EnableConfigurationProperties(BlogProperties::class)
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
application.properties에 프로퍼티를 설정한다.
src/main/resources/application.properties
blog.title=Blog
blog.banner.title=Warning
blog.banner.content=The Blog will be down tomorrow
controller에서 사용해보자
src/main/kotlin/com/example/demo/HtmlController.kt
@Controller
class HtmlController (private val repository: ArticleRepositoriy,
private val properties: BlogProperties) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"] = properties.title
model["banner"] = properties.banner
model["articles"] = repository.findAllByOrderByAddedAtDesc().map {
it.render()
}
return "blog"
}
// ...
마지막으로 무스타치에 적용해준다
src/main/kotlin/com/example/demo/HtmlController.kt
{{> header}}
<h1>{{title}}</h1>
<div class="articles">
{{#banner.title}}
<section>
<header class="banner">
<h2 class="banner-title">{{banner.title}}</h2>
</header>
<div class="banner-content">
{{banner.content}}
</div>
</section>
{{/banner.title}}
</div>
{{> footer}}
http://localhost:8080/에서 결과를 확인할 수 있다
Http API를 생성하고 테스트했고, 프로퍼티 설정까지 해보았다.
이걸로 스프링 부트와 코틀린 사용해서 블로그 웹 만들기는 끝이다
소스는 여기에서 확인 할 수 있다.