[#30] 코드 구조 개선 #31

Merged
pricelees merged 31 commits from refactor/#30 into main 2025-08-06 10:16:08 +00:00
3 changed files with 81 additions and 96 deletions
Showing only changes of commit 866c9a368f - Show all commits

View File

@ -1,25 +1,24 @@
package roomescape.auth.web package roomescape.auth.web
import com.ninjasquad.springmockk.SpykBean import com.ninjasquad.springmockk.MockkBean
import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.business.AuthService import roomescape.auth.business.AuthService
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.auth.exception.AuthException
import roomescape.common.exception.CommonErrorCode import roomescape.common.exception.CommonErrorCode
import roomescape.common.exception.ErrorCode import roomescape.common.exception.ErrorCode
import roomescape.member.exception.MemberErrorCode
import roomescape.member.exception.MemberException
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.RoomescapeApiTest import roomescape.util.RoomescapeApiTest
@WebMvcTest(controllers = [AuthController::class]) @WebMvcTest(controllers = [AuthController::class])
class AuthControllerTest( class AuthControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
val mockMvc: MockMvc
) : RoomescapeApiTest() {
@SpykBean @MockkBean
private lateinit var authService: AuthService private lateinit var authService: AuthService
val userRequest: LoginRequest = MemberFixture.userLoginRequest() val userRequest: LoginRequest = MemberFixture.userLoginRequest()
@ -32,12 +31,8 @@ class AuthControllerTest(
val expectedToken = "expectedToken" val expectedToken = "expectedToken"
every { every {
memberFinder.findByEmailAndPassword(userRequest.email, userRequest.password) authService.login(userRequest)
} returns user } returns LoginResponse(expectedToken)
every {
jwtHandler.createToken(user.id!!)
} returns expectedToken
Then("토큰을 반환한다.") { Then("토큰을 반환한다.") {
runPostTest( runPostTest(
@ -52,12 +47,13 @@ class AuthControllerTest(
} }
When("회원을 찾지 못하면") { When("회원을 찾지 못하면") {
val expectedError = AuthErrorCode.LOGIN_FAILED
every { every {
memberFinder.findByEmailAndPassword(userRequest.email, userRequest.password) authService.login(userRequest)
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND) } throws AuthException(expectedError)
Then("에러 응답을 받는다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.LOGIN_FAILED
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
@ -68,6 +64,7 @@ class AuthControllerTest(
} }
} }
} }
When("입력 값이 잘못되면") { When("입력 값이 잘못되면") {
val expectedErrorCode: ErrorCode = CommonErrorCode.INVALID_INPUT_VALUE val expectedErrorCode: ErrorCode = CommonErrorCode.INVALID_INPUT_VALUE
@ -97,6 +94,10 @@ class AuthControllerTest(
loginAsUser() loginAsUser()
Then("회원의 이름과 권한을 응답한다") { Then("회원의 이름과 권한을 응답한다") {
every {
authService.checkLogin(user.id!!)
} returns LoginCheckResponse(user.name, user.role.name)
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
@ -110,14 +111,12 @@ class AuthControllerTest(
When("토큰은 있지만 회원을 찾을 수 없으면") { When("토큰은 있지만 회원을 찾을 수 없으면") {
val invalidMemberId: Long = -1L val invalidMemberId: Long = -1L
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId
every { every { authService.checkLogin(invalidMemberId) } throws AuthException(expectedError)
memberFinder.findById(invalidMemberId)
} throws MemberException(MemberErrorCode.MEMBER_NOT_FOUND)
Then("에러 응답을 받는다.") { Then("에러 응답을 받는다.") {
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
@ -132,13 +131,11 @@ class AuthControllerTest(
val endpoint = "/logout" val endpoint = "/logout"
When("토큰으로 memberId 조회가 가능하면") { When("토큰으로 memberId 조회가 가능하면") {
every { loginAsUser()
jwtHandler.getMemberIdFromToken(any())
} returns 1L
every { every {
memberFinder.findById(1L) authService.logout(user.id!!)
} returns MemberFixture.create(id = 1L) } just Runs
Then("정상 응답한다.") { Then("정상 응답한다.") {
runPostTest( runPostTest(

View File

@ -1,68 +1,53 @@
package roomescape.member.controller package roomescape.member.controller
import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
import io.kotest.assertions.assertSoftly import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.every import io.mockk.every
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.MockMvc
import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthErrorCode
import roomescape.member.business.MemberService import roomescape.member.business.MemberService
import roomescape.member.exception.MemberErrorCode import roomescape.member.exception.MemberErrorCode
import roomescape.member.exception.MemberException import roomescape.member.exception.MemberException
import roomescape.member.implement.MemberWriter
import roomescape.member.infrastructure.persistence.Role import roomescape.member.infrastructure.persistence.Role
import roomescape.member.web.MemberController import roomescape.member.web.*
import roomescape.member.web.MemberRetrieveListResponse
import roomescape.member.web.SignupRequest
import roomescape.util.MemberFixture import roomescape.util.MemberFixture
import roomescape.util.RoomescapeApiTest import roomescape.util.RoomescapeApiTest
import kotlin.random.Random import kotlin.random.Random
@WebMvcTest(controllers = [MemberController::class]) @WebMvcTest(controllers = [MemberController::class])
class MemberControllerTest( class MemberControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
@Autowired private val mockMvc: MockMvc
) : RoomescapeApiTest() {
@SpykBean
private lateinit var memberService: MemberService
@MockkBean @MockkBean
private lateinit var memberWriter: MemberWriter private lateinit var memberService: MemberService
init { init {
given("GET /members 요청을") { given("GET /members 요청을") {
val endpoint = "/members" val endpoint = "/members"
val response = listOf(
every { memberFinder.findAll() } returns listOf(
MemberFixture.create(id = Random.nextLong(), name = "name1"), MemberFixture.create(id = Random.nextLong(), name = "name1"),
MemberFixture.create(id = Random.nextLong(), name = "name2"), MemberFixture.create(id = Random.nextLong(), name = "name2"),
MemberFixture.create(id = Random.nextLong(), name = "name3"), MemberFixture.create(id = Random.nextLong(), name = "name3"),
) ).toRetrieveListResponse()
every { memberService.findMembers() } returns response
`when`("관리자가 보내면") { `when`("관리자가 보내면") {
loginAsAdmin() loginAsAdmin()
then("성공한다.") { then("성공한다.") {
val result: String = runGetTest( val result: MemberRetrieveListResponse = runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,
) { ) {
status { isOk() } status { isOk() }
}.andReturn().response.contentAsString }.andReturn().readValue(MemberRetrieveListResponse::class.java)
val response: MemberRetrieveListResponse = readValue(
responseJson = result,
valueType = MemberRetrieveListResponse::class.java
)
assertSoftly(response.members) { assertSoftly(result.members) {
it.size shouldBe 3 it.size shouldBe response.members.size
it.map { m -> m.name } shouldContainAll listOf("name1", "name2", "name3") it.map { m -> m.name } shouldContainAll response.members.map { m -> m.name }
} }
} }
} }
@ -107,14 +92,14 @@ class MemberControllerTest(
) )
`when`("같은 이메일이 없으면") { `when`("같은 이메일이 없으면") {
every { every {
memberWriter.create(any(), any(), any(), any()) memberService.createMember(request)
} returns MemberFixture.create( } returns MemberFixture.create(
id = 1, id = 1,
name = request.name, name = request.name,
account = request.email, account = request.email,
password = request.password, password = request.password,
role = Role.MEMBER role = Role.MEMBER
) ).toSignupResponse()
then("id과 이름을 담아 성공 응답") { then("id과 이름을 담아 성공 응답") {
runPostTest( runPostTest(
@ -130,13 +115,12 @@ class MemberControllerTest(
} }
`when`("같은 이메일이 있으면") { `when`("같은 이메일이 있으면") {
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
every { every {
memberWriter.create(request.name, request.email, request.password, Role.MEMBER) memberService.createMember(request)
} throws MemberException(MemberErrorCode.DUPLICATE_EMAIL) } throws MemberException(expectedError)
then("에러 응답") { then("에러 응답") {
val expectedError = MemberErrorCode.DUPLICATE_EMAIL
runPostTest( runPostTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = endpoint, endpoint = endpoint,

View File

@ -21,18 +21,15 @@ import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
@WebMvcTest(TimeController::class) @WebMvcTest(TimeController::class)
class TimeControllerTest( class TimeControllerTest(val mockMvc: MockMvc) : RoomescapeApiTest() {
val mockMvc: MockMvc,
) : RoomescapeApiTest() {
@MockkBean @MockkBean
private lateinit var timeService: TimeService private lateinit var timeService: TimeService
init { init {
Given("등록된 모든 시간을 조회할 때") { Given("GET /times 요청을") {
val endpoint = "/times" val endpoint = "/times"
When("관리자인 경우") { When("관리자가 보내면") {
beforeTest { beforeTest {
loginAsAdmin() loginAsAdmin()
} }
@ -59,7 +56,7 @@ class TimeControllerTest(
} }
} }
When("관리자가 아닌 경우") { When("관리자가 보내지 않았다면") {
loginAsUser() loginAsUser()
val expectedError = AuthErrorCode.ACCESS_DENIED val expectedError = AuthErrorCode.ACCESS_DENIED
@ -79,10 +76,10 @@ class TimeControllerTest(
} }
} }
Given("시간을 추가할 때") { Given("POST /times 요청을") {
val endpoint = "/times" val endpoint = "/times"
When("관리자인 경우") { When("관리자가 보낼 때") {
beforeTest { beforeTest {
loginAsAdmin() loginAsAdmin()
} }
@ -143,7 +140,7 @@ class TimeControllerTest(
} }
} }
When("관리자가 아닌 경우") { When("관리자가 보내지 않았다면") {
loginAsUser() loginAsUser()
Then("예외 응답") { Then("예외 응답") {
@ -160,10 +157,10 @@ class TimeControllerTest(
} }
} }
Given("시간을 삭제할 때") { Given("DELETE /times/{id} 요청을") {
val endpoint = "/times/1" val endpoint = "/times/1"
When("관리자인 경우") { When("관리자가 보낼 때") {
beforeTest { beforeTest {
loginAsAdmin() loginAsAdmin()
} }
@ -222,7 +219,7 @@ class TimeControllerTest(
} }
} }
When("관리자가 아닌 경우") { When("관리자가 보내지 않았다면") {
loginAsUser() loginAsUser()
Then("예외 응답") { Then("예외 응답") {
@ -238,28 +235,21 @@ class TimeControllerTest(
} }
} }
Given("날짜, 테마가 주어졌을 때") { Given("GET /times/search?date={date}&themeId={themeId} 요청을 ") {
beforeTest {
loginAsUser()
}
val date: LocalDate = LocalDate.now() val date: LocalDate = LocalDate.now()
val themeId = 1L val themeId = 1L
When("테마를 찾을 수 있으면") { When("회원이 보낼 때") {
Then("해당 날짜와 테마에 대한 예약 여부가 담긴 모든 시간을 응답") { beforeTest {
loginAsUser()
}
Then("정상 응답") {
val response = TimeWithAvailabilityListResponse( val response = TimeWithAvailabilityListResponse(
listOf( listOf(
TimeWithAvailabilityResponse(id = 1L, startAt = LocalTime.now(), isAvailable = true), TimeWithAvailabilityResponse(1L, LocalTime.of(10, 0), true),
TimeWithAvailabilityResponse( TimeWithAvailabilityResponse(2L, LocalTime.of(10, 1), false),
id = 2L, TimeWithAvailabilityResponse(3L, LocalTime.of(10, 2), true)
startAt = LocalTime.now().plusMinutes(30),
isAvailable = false
),
TimeWithAvailabilityResponse(
id = 1L,
startAt = LocalTime.now().plusHours(1),
isAvailable = true
),
) )
) )
every { every {
@ -285,15 +275,12 @@ class TimeControllerTest(
this[1].isAvailable shouldBe response.times[1].isAvailable this[1].isAvailable shouldBe response.times[1].isAvailable
} }
} }
} Then("테마를 찾을 수 없으면 예외 응답") {
val expectedError = ThemeErrorCode.THEME_NOT_FOUND
every {
timeService.findTimesWithAvailability(date, themeId)
} throws ThemeException(expectedError)
When("테마를 찾을 수 없으면") {
val expectedError = ThemeErrorCode.THEME_NOT_FOUND
every {
timeService.findTimesWithAvailability(date, themeId)
} throws ThemeException(expectedError)
Then("예외 응답") {
runGetTest( runGetTest(
mockMvc = mockMvc, mockMvc = mockMvc,
endpoint = "/times/search?date=$date&themeId=$themeId", endpoint = "/times/search?date=$date&themeId=$themeId",
@ -303,6 +290,23 @@ class TimeControllerTest(
} }
} }
} }
When("비회원이 보내면") {
doNotLogin()
Then("예외 응답") {
val expectedError = AuthErrorCode.MEMBER_NOT_FOUND
runGetTest(
mockMvc = mockMvc,
endpoint = "/times/search?date=$date&themeId=$themeId",
) {
status { isEqualTo(expectedError.httpStatus.value()) }
jsonPath("$.code", equalTo(expectedError.errorCode))
}
}
}
} }
} }
} }