```
class SignUpApiTest : BehaviorSpec({
lateinit var mockMvc: MockMvc
@MockK
lateinit var accountRepository: AccountRepository
beforeTest {
accountRepository = mockk<AccountRepository>()
mockMvc = MockMvcBuilders.standaloneSetup(AccountController(AccountServiceImpl(accountRepository))).build()
}
Given("사용자가 회원가입을 시도할때") {
val email = "test@gmail.com"
val password = "password"
val signForm = SignForm(email, password)
val requestBody = jacksonObjectMapper().writeValueAsString(signForm)
When("중복된 이메일이 존재한다면") {
every { accountRepository.existsByEmail(email) } returns true
Then("409에러를 반환한다.")
val result = mockMvc
.perform(
post("/account/sign-up").content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
).andReturn()
val status = result.response.status
status shouldBe HttpStatus.CONFLICT.value()
}
When("중복된 이메일이 존재하지 않는다면") {
every { accountRepository.existsByEmail(any()) } returns false
every { accountRepository.save(any()) } returns Account(email, password)
Then("200 OK, 'status' to 'success', accountId 응답을 반환한다.") {
val result = mockMvc
.perform(
post("/account/sign-up").content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
).andReturn()
val status = result.response.status
status shouldBe HttpStatus.OK.value()
val contentsMap = JSONObject(result.response.contentAsString)
contentsMap["status"] shouldBe "success"
}
}
}
})
```
혼자 kotlin, kotest, mockk 공부하면서 단순한 회원가입 로직 테스트 짜봤는데
en번째 When "중복된 이메일이 존재하지 않는다면" 테스트에서
Request processing failed: io.mockk.MockKException: no answer found for AccountRepository(#5).existsByEmail(test@gmail.com) among the configured answers: ()
라는 에러가 발생합니다...
mocking이 안되었다고 발생하는 에러인 것으로 생각하고 있는데,
repository도 mockk<AccountRepository> 로 모킹 되었고
무엇보다 첫번째 테스트는 통과하는데 왜 두번째 테스트는 통과를 못하는지 진짜 전혀 이해를 못하겠습니다...
혹시 파악 가능 하시다면 도움 부탁드립니다...
Given(""){
When("") {
Then("") {
every { accountRepository.existsByEmail(email))} returns false
...
}
}}
구조로 모킹을 Then으로 옮겨주니깐 의도한대로 동작하네요...
이유는 모르겠습니다...
kotest life cycle에 따라 beforeTest가 아니라 beforeContainer 에서 모킹해줘야 하네요...
하... 이걸로 시간을 얼마나 쓴건지...
https://kotest.io/docs/framework/testing-styles.html
To use Kotest, create a class file that extends one of the test styles. Then inside an init { } block, create your test cases. The following table contains the test styles you can pick from along with examples.
Spec 에 init 블록 내에서 테스트 코드를 작성하라 되어있습니다.
```
class CancelUnregisterFcServiceSpec : BehaviorSpec() {
// 테스트 별 격리 수준
override fun isolationMode(): IsolationMode = IsolationMode.InstancePerTest
init {
// 테스트 공통 변수 초기화
Given(...) {
// 기능 별 변수 초기화
When(..) {
// 테스트 코드
}
}
}
}
```
기본 구조는 위와 같고, 이렇게 작성하면 각 테스트에 사용되는 mocking 된 객체들의 verify 시 exactly 횟수 등이 다른 테스트에 영향이 미칠 수 있습니다.
그건 Spec 블록 첫 부분의 격리 모드를 조정하면 됩니다.
이렇게 한번 작성해보세요. 그리고 mock 은 개인적으로 코틀린에서는 어노테이션으로 선언하는 방법은 사용하지 않는 편입니다. 모두 val testRepository TestRepository = mockk() 요런식으로 선언하는 방법을 선호합니다.
잘 되길 바랍니다.
말씀하신대로 구조를 수정해 봤습니다.
```
class SignUpApiTest : BehaviorSpec() {
override fun isolationMode(): IsolationMode = IsolationMode.InstancePerTest
init {
val accountRepository = mockk<AccountRepository>()
val mockMvc = MockMvcBuilders.standaloneSetup(AccountController(AccountServiceImpl(accountRepository))).build()
val email = "test@gmail.com"
val password = "password"
val signForm = SignForm(email, password)
val requestBody = jacksonObjectMapper().writeValueAsString(signForm)
Given("특정 이메일로 생성된 계정이 이미 존재하는 경우") {
every { accountRepository.existsByEmail(any()) } returns true
When("사용자가 해당 이메일로 회원 가입을 시도한다면") {
val result = mockMvc
.perform(
post("/account/sign-up").content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
).andReturn()
Then("409에러로 응답한다.") {
result.response.status shouldBe HttpStatus.CONFLICT.value()
}
}
}
Given("특정 이메일로 생성된 계정이 존재하지 않는 경우") {
every { accountRepository.existsByEmail(any()) } returns false
every { accountRepository.save(any()) } returns Account(email, password)
When("사용자가 해당 이메일로 회원가입을 시도한다면") {
val result = mockMvc
.perform(
post("/account/sign-up").content(requestBody)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
).andReturn()
Then("회원 정보를 db에 저장하고") {
//TODO: 나중에 H2DB를 이용한 저장 테스트 구현
}
Then("상태코드는 200 ok로 응답한다") {
result.response.status shouldBe HttpStatus.OK.value()
}
Then("body는 'status':'success'로 응답한다") {
val contentMap = JSONObject(result.response.contentAsString)
contentMap["status"] shouldBe "success"
}
}
}
}
}
```
mockmvc 도 andExpect 와 kotlin dsl 를 사용한 문법으로 좀 더 유연하게 When Then 절을 구성할 수 있는데, 요것도 한번 해보세요. json 변환 등 알아서 해주어서 정말 편리합니다.
문법은 mockmvc + kotlin dsl 정도로 검색해보시면 더 많은 정보를 얻을 수 있습니다.
(jsonPath 문법은 간편하고, 강력합니다.)
When("without body") {
val result = mockMvc.get("/api/auth/login/admin")
Then("failed by unprocess entity") {
result
.andExpect {
status { isUnprocessableEntity() }
}
.andExpect {
jsonPath("$.message") { value("비정상적인 요청입니다.") }
}
}
}
override fun isolationMode(): IsolationMode = IsolationMode.InstancePerTest
init {
val accountRepository = mockk<AccountRepository>()
val mockMvc = MockMvcBuilders.standaloneSetup(AccountController(AccountServiceImpl(accountRepository))).build()
val email = "test@gmail.com"
val password = "password"
val signForm = SignForm(email, password)
val requestBody = jacksonObjectMapper().writeValueAsString(signForm)
Given("특정 이메일로 생성된 계정이 이미 존재하는 경우") {
every { accountRepository.existsByEmail(any()) } returns true
When("사용자가 해당 이메일로 회원 가입을 시도한다면") {
val result = mockMvc.post("/account/sign-up") {
content = requestBody
contentType = MediaType.APPLICATION_JSON
accept = MediaType.APPLICATION_JSON
}
Then("409에러로 응답한다.") {
result.andExpect {
status { isConflict() }
}
}
}
}
Given("특정 이메일로 생성된 계정이 존재하지 않는 경우") {
every { accountRepository.existsByEmail(any()) } returns false
every { accountRepository.save(any()) } returns Account(email, password)
When("사용자가 해당 이메일로 회원가입을 시도한다면") {
val result = mockMvc.post("/account/sign-up") {
content = requestBody
contentType = MediaType.APPLICATION_JSON
accept = MediaType.APPLICATION_JSON
}
Then("회원 정보를 db에 저장하고") {
//TODO: 나중에 H2DB를 이용한 저장 테스트 구현
}
Then("상태코드는 200 ok로 응답한다") {
result.andExpect {
status { isOk() }
}
}
Then("body는 'status':'success'로 응답한다") {
result.andExpect {
jsonPath("$.status") { value("success" )}
}
}
}
}
}
조언 주신대로 위와 같이 수정 했습니다!
감사합니다!
혼자 코프링 익히면서 하는중이라 노하우 등이 부족했는데 테스트 하는데 도움이 많이 되었습니다!
자바만 하다 그래서 그런지 kotlin dsl은 아직 낯서네요