-
코틀린에서 테스트 하기 (⏳)KOTLIN 2022. 11. 27. 14:16
자바에서 실행했던 인수테스트와 API테스트를 코틀린에서도 적용하는 방법을 알아보자.
API 테스트
먼저 API테스트는 간단하게 mock()을 주입해줌으로서 구현할 수 있다. 먼저 dependency에 추가해준다.
dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc") testImplementation("io.mockk:mockk:1.12.0") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.6.20") testImplementation("org.hamcrest:hamcrest:2.2") }
간단한 API를 구현해본다. 테스트 코드는 슬랙 테스트용으로 작성하였을 때의 코드를 가져왔다.
@RestController @RequestMapping("/api/restaurants") class RestaurantController(val restaurantService: RestaurantService) { @GetMapping fun findAll() = ResponseEntity(restaurantService.findAll(), HttpStatus.OK) }
테스트 코드는 다음과 같이 간단한게 작성할 수 있다.
import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.get @WebMvcTest @AutoConfigureMockMvc internal class RestaurantControllerTest { private val baseUrl = "/api/restaurants" @Autowired private lateinit var mockMvc: MockMvc @Test fun `should return all restaurants`() { // when / then mockMvc.get(baseUrl) .andDo { print() } .andExpect { status { isOk() } content { contentType(MediaType.APPLICATION_JSON) } } } }
인수테스트
인수테스트의 경우 기존 자바에서 RestAssured를 이용해서 테스트를 진행하였는데, 코틀린에서도 실제로 인수테스트시 restAssured를 사용하여 인수테스트를 진행하고 있는지 파악중에 있다. 🥹 또, 우선은 기존에 했던 자바 코드를 거의 복붙식으로 구현해서 사용하고 있기 때문에 이 부분은 좀더 알아보고 수정할 예정이다. ✨✨✨
그래서 우선 restAssured를 사용하여 인수테스트를 구현하는 방법을 사용하였다. 먼저 dependency를 추가해준다.
dependencies { testImplementation("io.mockk:mockk:1.12.0") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.6.20") // 테스트 가독성을 높여주는 도구 testImplementation("org.hamcrest:hamcrest:2.2") // 인수테스트를 위한 restAssured 의존성 추가 testImplementation("io.rest-assured:kotlin-extensions:5.0.1") }
테스트를 반복적으로 진행하고 성공시키기 위해서 database를 비워주는 역할을 수행하는 DatabaseCleanUp을 구현해준다.
package slack.restaurant_bot import com.google.common.base.CaseFormat import org.springframework.beans.factory.InitializingBean import org.springframework.stereotype.Component import org.springframework.test.context.ActiveProfiles import org.springframework.transaction.annotation.Transactional import javax.persistence.Entity import javax.persistence.EntityManager import javax.persistence.PersistenceContext @Component @ActiveProfiles("test") class DatabaseCleanup : InitializingBean { @PersistenceContext private val entityManager: EntityManager? = null private var tableNames: List<String>? = null override fun afterPropertiesSet() { tableNames = entityManager!!.metamodel.entities.filter { it.javaType.getAnnotation(Entity::class.java) != null } .map { CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, it.name) } .toList() } @Transactional fun execute() { entityManager!!.flush() entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate() for (tableName in tableNames!!) { createTruncateQuery(tableName) } entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate() } private fun createTruncateQuery(tableName: String) { entityManager!!.createNativeQuery("TRUNCATE TABLE $tableName").executeUpdate() } }
인수테스트 공통적으로 들어갈 설정을 따로 빼내어준 AcceptanceTest 를 작성한다.
package slack.restaurant_bot import io.restassured.RestAssured import io.restassured.builder.RequestSpecBuilder import io.restassured.config.LogConfig import io.restassured.config.RestAssuredConfig import io.restassured.filter.log.LogDetail import io.restassured.http.ContentType import io.restassured.specification.RequestSpecification import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.TestInstance import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.server.LocalServerPort @TestInstance(TestInstance.Lifecycle.PER_CLASS) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class AcceptanceTest { companion object { lateinit var requestSpecification: RequestSpecification } @LocalServerPort var port: Int = 0 @Autowired lateinit var databaseCleanup: DatabaseCleanup @BeforeEach fun setup(){ if (RestAssured.port == RestAssured.UNDEFINED_PORT) { RestAssured.port = port databaseCleanup.afterPropertiesSet() } databaseCleanup.execute() val logConfig = LogConfig.logConfig() .enableLoggingOfRequestAndResponseIfValidationFails(LogDetail.ALL) val config = RestAssuredConfig.config().logConfig(logConfig) requestSpecification = RequestSpecBuilder() .setBasePath("/api") .setContentType(ContentType.JSON) .setRelaxedHTTPSValidation() .setConfig(config) .build() } }
이제 인수테스트를 작성해준다. 이 부분까지 모두 자바와 거의 동일하게 진행된다.
import io.restassured.module.kotlin.extensions.Extract import io.restassured.module.kotlin.extensions.Given import io.restassured.module.kotlin.extensions.Then import io.restassured.module.kotlin.extensions.When import org.apache.http.HttpStatus import org.junit.jupiter.api.Test import slack.restaurant_bot.restaurant.dto.RestaurantRequest import slack.restaurant_bot.model.RestaurantTest class RestaurantAcceptanceTest : AcceptanceTest() { private val baseUrl = "/restaurants" private val firstRequest = RestaurantRequest( RestaurantTest.FIRST_RESTAURANT.name, RestaurantTest.FIRST_RESTAURANT.getCategory(), RestaurantTest.FIRST_RESTAURANT.getMainMenu(), RestaurantTest.FIRST_RESTAURANT.getFlavor(), RestaurantTest.FIRST_RESTAURANT.getLocation(), RestaurantTest.FIRST_RESTAURANT.getLink() ) private val secondRequest = RestaurantRequest( RestaurantTest.SECOND_RESTAURANT.name, RestaurantTest.SECOND_RESTAURANT.getCategory(), RestaurantTest.SECOND_RESTAURANT.getMainMenu(), RestaurantTest.SECOND_RESTAURANT.getFlavor(), RestaurantTest.SECOND_RESTAURANT.getLocation(), RestaurantTest.SECOND_RESTAURANT.getLink() ) @Test fun `manage restaurant `() { // when / then `register a restaurant with given request`(firstRequest) `register a restaurant with given request`(secondRequest) // when / then val id = `find a restaurant with given name`(RestaurantTest.FIRST_RESTAURANT.name) // when / then `find all restaurants`() // when / then `find restaurants recommendations by category`() // when / then `find restaurants recommendations by flavor`() // when / then `update restaurant rating with given rating`(id) // when / then `delete restaurant with given id`(id) } fun `register a restaurant with given request`(request: RestaurantRequest) { // when Given { spec(requestSpecification) body(request) } When { post(baseUrl) } Then { statusCode(HttpStatus.SC_CREATED) } } fun `find a restaurant with given name`(name: String): Long { return Given { spec(requestSpecification) param("name", name) } When { get(baseUrl) } Then { statusCode(HttpStatus.SC_OK) } Extract { body().jsonPath().getLong("id") } } fun `find all restaurants`() { Given { spec(requestSpecification) } When { get(baseUrl) } Then { statusCode(HttpStatus.SC_OK) } Extract { println(body().asString()) } } fun `find restaurants recommendations by category`() { Given { spec(requestSpecification) param("category", "한식") } When { get("$baseUrl/recommendations") } Then { statusCode(HttpStatus.SC_OK) } Extract { println(body().asString()) } } fun `find restaurants recommendations by flavor`() { Given { spec(requestSpecification) param("flavor", "맛있음") } When { get("$baseUrl/recommendations") } Then { statusCode(HttpStatus.SC_OK) } Extract { println(body().asString()) } } fun `update restaurant rating with given rating`(id: Long) { Given { spec(requestSpecification) param("rating", 2) param("priceRating", 4) } When { patch("$baseUrl/$id") } Then { statusCode(HttpStatus.SC_OK) } Extract { println(body().asString()) } } fun `delete restaurant with given id`(id: Long) { Given { spec(requestSpecification) } When { delete("$baseUrl/$id") } Then { statusCode(HttpStatus.SC_NO_CONTENT) } } }
(참고한 사이트)
Kotlin Spring Boot Unit Testing & Integration Testing with JUnit5 and Mockk ✔🚀
Let’s make a Simple CRUD application with MongoDB and Kotlin SpringBoot and do Integration and Unit testing with Junit5 and Mockk.
medium.com
https://github.com/rest-assured/rest-assured/wiki/Kotlin
GitHub - rest-assured/rest-assured: Java DSL for easy testing of REST services
Java DSL for easy testing of REST services. Contribute to rest-assured/rest-assured development by creating an account on GitHub.
github.com
https://code.haleby.se/2019/09/06/rest-assured-in-kotlin/
REST Assured in Kotlin – Coding all the things
code.haleby.se
https://sabarada.tistory.com/191
[mockk] 코틀린 테스트 프레임워크에 대해서 알아보자
안녕하세요. 오늘은 코틀린 테스트 프레임워크인 mockk의 사용법에 대해서 알아보는 시간을 가져보도록 하겠습니다. mockk framework mockk는 코틀린 스타일로 테스트 코드를 작성할 수 있도록 도와주
sabarada.tistory.com
https://zzang9ha.tistory.com/382
Spring Boot + MockMvc 테스트(feat. Kotlin)
📎 Spring Boot + MockMvc 테스트 안녕하세요, 이번 시간에 정리할 내용은 스프링 부트와 MockMvc를 통한 GET, POST 등의 API를 테스트하는 법에 대해 정리해보도록 하겠습니다. 전체 코드는 깃허브에서
zzang9ha.tistory.com
GitHub - rest-assured/rest-assured: Java DSL for easy testing of REST services
Java DSL for easy testing of REST services. Contribute to rest-assured/rest-assured development by creating an account on GitHub.
github.com
https://code.haleby.se/2019/09/06/rest-assured-in-kotlin/
REST Assured in Kotlin – Coding all the things
code.haleby.se
Kotlin Spring Boot Unit Testing & Integration Testing with JUnit5 and Mockk ✔🚀
Let’s make a Simple CRUD application with MongoDB and Kotlin SpringBoot and do Integration and Unit testing with Junit5 and Mockk.
medium.com
https://github.com/rest-assured/rest-assured/wiki/Kotlin
GitHub - rest-assured/rest-assured: Java DSL for easy testing of REST services
Java DSL for easy testing of REST services. Contribute to rest-assured/rest-assured development by creating an account on GitHub.
github.com
https://sabarada.tistory.com/191
[mockk] 코틀린 테스트 프레임워크에 대해서 알아보자
안녕하세요. 오늘은 코틀린 테스트 프레임워크인 mockk의 사용법에 대해서 알아보는 시간을 가져보도록 하겠습니다. mockk framework mockk는 코틀린 스타일로 테스트 코드를 작성할 수 있도록 도와주
sabarada.tistory.com
'KOTLIN' 카테고리의 다른 글
Enum의 property로 Json 변환 대상 특정하기 (0) 2023.11.01 코틀린에서 Data클래스에 JPA를 사용할 때 주의해야 할 점 (0) 2022.11.27 📕 코틀린 동시성 프로그래밍 - Ch.2) Coroutine in Action (0) 2022.11.11 📕 코틀린 동시성 프로그래밍 - Ch.1) Hello, Concurrent World! (0) 2022.11.08 코프링(코틀린 + 스프링부트) + 구글 스프레드 시트로 슬랙봇 만들기 - ④ 구글 스프레드 시트 사용하기 (0) 2022.10.22