스프링 부트와 코틀린 사용해서 블로그 웹 만들기 (4)

KKTRKKT·2021년 12월 11일
0

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가 제대로 작동하는지 검증한다.

프로퍼티 설정하기

  • 메뉴에 inteliJ IDEA | Plugins | Spring Boot enabled 체크
  • 메뉴에 inteliJ IDEA | Build, Execution, Deployment | Compiler | Annotation Processors | Enable annotation processing 체크

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를 생성하고 테스트했고, 프로퍼티 설정까지 해보았다.
이걸로 스프링 부트와 코틀린 사용해서 블로그 웹 만들기는 끝이다

소스는 여기에서 확인 할 수 있다.

Reference

https://spring.io/guides/tutorials/spring-boot-kotlin/

profile
https://kktrkkt.github.io/

0개의 댓글

관련 채용 정보