From 19da58c1f38046a906e41b578132afa082ac327f Mon Sep 17 00:00:00 2001 From: pricelees Date: Tue, 15 Jul 2025 07:21:16 +0000 Subject: [PATCH] =?UTF-8?q?[#9]=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A5=BC=20@SpringbootTest=EC=97=90=EC=84=9C=20@WebMvcTest=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98=20(#1?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 관련 이슈 및 PR **PR과 관련된 이슈 번호** - #9 ## ✨ 작업 내용 기존에 정의된 \@SpringbootTest 기반 API 테스트를 MockK + WebMvcTest 기반으로 전환하였음. - 기존 \@SpringbootTest 기반의 테스트도 대부분 로직을 mocking하고 있기에, 불필요한 Spring Context 로딩 비용을 줄일 수 있다고 판단하였음. ## 🧪 테스트 - 기존에 있는 3개의 클래스를 리팩터링 전후 비교 결과 리팩터링 후 소요시간이 3회 측정 평균 1.5736초에서 0.6153초로 61% 정도 감소하였음. ## 📚 참고 자료 및 기타 Reviewed-on: https://gitea.pricelees.me/pricelees/roomescape-refactored/pulls/10 Co-authored-by: pricelees Co-committed-by: pricelees --- .../roomescape/auth/web/AuthControllerTest.kt | 137 ++++++++++----- .../response/RoomescapeApiResponseKTTest.kt | 158 ------------------ .../member/controller/MemberControllerTest.kt | 61 ++++--- src/test/java/roomescape/util/Fixtures.kt | 2 + .../java/roomescape/util/RoomescapeApiTest.kt | 104 +++++++----- .../java/roomescape/util/TestAnnotations.kt | 11 -- .../roomescape/view/PageControllerTest.kt | 101 +++++++---- 7 files changed, 271 insertions(+), 303 deletions(-) delete mode 100644 src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt delete mode 100644 src/test/java/roomescape/util/TestAnnotations.kt diff --git a/src/test/java/roomescape/auth/web/AuthControllerTest.kt b/src/test/java/roomescape/auth/web/AuthControllerTest.kt index 0f23f160..a4f575d1 100644 --- a/src/test/java/roomescape/auth/web/AuthControllerTest.kt +++ b/src/test/java/roomescape/auth/web/AuthControllerTest.kt @@ -1,23 +1,34 @@ package roomescape.auth.web +import com.ninjasquad.springmockk.SpykBean import io.mockk.every import org.hamcrest.Matchers.containsString -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.data.repository.findByIdOrNull +import org.springframework.test.web.servlet.MockMvc +import roomescape.auth.service.AuthService import roomescape.common.exception.ErrorType import roomescape.util.MemberFixture import roomescape.util.RoomescapeApiTest -class AuthControllerTest : RoomescapeApiTest() { +@WebMvcTest(controllers = [AuthController::class]) +class AuthControllerTest( + @Autowired mockMvc: MockMvc +) : RoomescapeApiTest() { + + @SpykBean + private lateinit var authService: AuthService val userRequest: LoginRequest = MemberFixture.userLoginRequest() init { Given("로그인 요청을 보낼 때") { - val endpoint: String = "/login" + val endpoint = "/login" When("로그인에 성공하면") { - val expectedToken: String = "expectedToken" + val expectedToken = "expectedToken" every { memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password) @@ -28,12 +39,19 @@ class AuthControllerTest : RoomescapeApiTest() { } returns expectedToken Then("토큰을 쿠키에 담아 응답한다") { - runPostTest(endpoint, body = MemberFixture.userLoginRequest()) { - statusCode(200) - cookie("accessToken", expectedToken) - header("Set-Cookie", containsString("Max-Age=1800")) - header("Set-Cookie", containsString("HttpOnly")) - header("Set-Cookie", containsString("Secure")) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + body = userRequest, + log = true + ) { + status { isOk() } + header { + string("Set-Cookie", containsString("accessToken=$expectedToken")) + string("Set-Cookie", containsString("Max-Age=1800")) + string("Set-Cookie", containsString("HttpOnly")) + string("Set-Cookie", containsString("Secure")) + } } } } @@ -44,10 +62,14 @@ class AuthControllerTest : RoomescapeApiTest() { } returns null Then("400 에러를 응답한다") { - runPostTest(endpoint, body = userRequest) { - log().all() - statusCode(400) - body("errorType", `is`(ErrorType.MEMBER_NOT_FOUND.name)) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + body = userRequest, + log = true + ) { + status { isBadRequest() } + jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name)) } } } @@ -57,36 +79,47 @@ class AuthControllerTest : RoomescapeApiTest() { Then("이메일 형식이 잘못된 경우") { val invalidRequest: LoginRequest = userRequest.copy(email = "invalid") - runPostTest(endpoint, body = invalidRequest) { - log().all() - statusCode(400) - body("message", `is`("이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com")) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + body = invalidRequest, + log = true + ) { + status { isBadRequest() } + jsonPath("$.message", containsString("이메일 형식이 일치하지 않습니다.")) } } Then("비밀번호가 공백인 경우") { val invalidRequest = userRequest.copy(password = " ") - runPostTest(endpoint, body = invalidRequest) { - log().all() - statusCode(400) - body("message", `is`("비밀번호는 공백일 수 없습니다.")) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + body = invalidRequest, + log = true + ) { + status { isBadRequest() } + jsonPath("$.message", containsString("비밀번호는 공백일 수 없습니다.")) } } } } Given("로그인 상태를 확인할 때") { - val endpoint: String = "/login/check" + val endpoint = "/login/check" When("로그인된 회원의 ID로 요청하면") { - every { jwtHandler.getMemberIdFromToken(any()) } returns user.id!! - every { memberRepository.findByIdOrNull(user.id!!) } returns user + loginAsUser() Then("회원의 이름을 응답한다") { - runGetTest(endpoint) { - statusCode(200) - body("data.name", `is`(user.name)) + runGetTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { isOk() } + jsonPath("$.data.name", equalTo(user.name)) } } } @@ -98,39 +131,55 @@ class AuthControllerTest : RoomescapeApiTest() { every { memberRepository.findByIdOrNull(invalidMemberId) } returns null Then("400 에러를 응답한다.") { - runGetTest(endpoint) { - statusCode(400) - body("errorType", `is`(ErrorType.MEMBER_NOT_FOUND.name)) + runGetTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { isBadRequest() } + jsonPath("$.errorType", equalTo(ErrorType.MEMBER_NOT_FOUND.name)) } } } } Given("로그아웃 요청을 보낼 때") { - val endpoint: String = "/logout" + val endpoint = "/logout" When("로그인 상태가 아니라면") { - setUpNotLoggedIn() + doNotLogin() Then("로그인 페이지로 이동한다.") { - runPostTest(endpoint) { - log().all() - statusCode(302) - header("Location", containsString("/login")) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { is3xxRedirection() } + header { + string("Location", "/login") + } } } } When("로그인 상태라면") { - setUpUser() - every { memberRepository.findByIdOrNull(user.id!!) } returns user + loginAsUser() Then("토큰의 존재 여부와 무관하게 토큰을 만료시킨다.") { - runPostTest(endpoint) { - log().all() - statusCode(200) - cookie("accessToken", "") - header("Set-Cookie", containsString("Max-Age=0")) + runPostTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { isOk() } + header { + string("Set-Cookie", containsString("Max-Age=0")) + string("Set-Cookie", containsString("accessToken=")) + string("Set-Cookie", containsString("Path=/")) + string("Set-Cookie", containsString("HttpOnly")) + string("Set-Cookie", containsString("Secure")) + } } } } diff --git a/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt b/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt deleted file mode 100644 index 447a3db7..00000000 --- a/src/test/java/roomescape/common/dto/response/RoomescapeApiResponseKTTest.kt +++ /dev/null @@ -1,158 +0,0 @@ -//package roomescape.common.dto.response -// -//import com.fasterxml.jackson.databind.ObjectMapper -//import com.ninjasquad.springmockk.MockkBean -//import com.ninjasquad.springmockk.SpykBean -//import io.kotest.core.spec.style.BehaviorSpec -//import org.hamcrest.CoreMatchers.equalTo -//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.get -//import org.springframework.test.web.servlet.post -//import org.springframework.web.bind.annotation.* -//import roomescape.auth.infrastructure.jwt.JwtHandler -//import roomescape.auth.web.support.AdminInterceptor -//import roomescape.auth.web.support.LoginInterceptor -//import roomescape.auth.web.support.MemberIdResolver -//import roomescape.common.exception.ErrorType -//import roomescape.member.business.MemberService -//import roomescape.member.infrastructure.persistence.MemberRepository -// -//@WebMvcTest(ApiResponseTestController::class) -//class RoomescapeApiResponseKTTest( -// @Autowired private val mockMvc: MockMvc -//) : BehaviorSpec() { -// @Autowired -// private lateinit var AdminInterceptor: AdminInterceptor -// -// @Autowired -// private lateinit var loginInterceptor: LoginInterceptor -// -// @Autowired -// private lateinit var memberIdResolver: MemberIdResolver -// -// @SpykBean -// private lateinit var memberService: MemberService -// -// @MockkBean -// private lateinit var memberRepository: MemberRepository -// -// @MockkBean -// private lateinit var jwtHandler: JwtHandler -// -// init { -// Given("성공 응답에") { -// val endpoint = "/success" -// When("객체 데이터를 담으면") { -// val id: Long = 1L -// val name = "name" -// Then("success=true, data={객체} 형태로 응답한다.") { -// mockMvc.post("$endpoint/$id/$name") { -// contentType = MediaType.APPLICATION_JSON -// }.andDo { -// print() -// }.andExpect { -// status { isOk() } -// jsonPath("$.success", equalTo(true)) -// jsonPath("$.data.id", equalTo(id.toInt())) -// jsonPath("$.data.name", equalTo(name)) -// } -// } -// } -// -// When("문자열 데이터를 담으면") { -// val message: String = "Hello, World!" -// -// Then("success=true, data={문자열} 형태로 응답한다.") { -// mockMvc.get("/success/$message") { -// contentType = MediaType.APPLICATION_JSON -// }.andDo { -// print() -// }.andExpect { -// status { isOk() } -// jsonPath("$.success", equalTo(true)) -// jsonPath("$.data", equalTo(message)) -// } -// } -// } -// } -// -// Given("실패 응답에") { -// val endpoint = "/fail" -// val objectMapper = ObjectMapper() -// -// When("errorType만 담으면") { -// Then("success=false, errorType={errorType}, message={errorType.description} 형태로 응답한다.") { -// mockMvc.post(endpoint) { -// contentType = MediaType.APPLICATION_JSON -// content = objectMapper.writeValueAsString(FailRequest(errorType = ErrorType.INTERNAL_SERVER_ERROR)) -// }.andDo { -// print() -// }.andExpect { -// status { isOk() } -// jsonPath("$.success", equalTo(false)) -// jsonPath("$.errorType", equalTo(ErrorType.INTERNAL_SERVER_ERROR.name)) -// jsonPath("$.message", equalTo(ErrorType.INTERNAL_SERVER_ERROR.description)) -// } -// } -// } -// -// When("errorType과 message를 담으면") { -// val message: String = "An error occurred" -// -// Then("success=false, errorType={errorType}, message={message} 형태로 응답한다.") { -// mockMvc.post(endpoint) { -// contentType = MediaType.APPLICATION_JSON -// content = objectMapper.writeValueAsString(FailRequest(errorType = ErrorType.INTERNAL_SERVER_ERROR, message = message)) -// }.andDo { -// print() -// }.andExpect { -// status { isOk() } -// jsonPath("$.success", equalTo(false)) -// jsonPath("$.errorType", equalTo(ErrorType.INTERNAL_SERVER_ERROR.name)) -// jsonPath("$.message", equalTo(message)) -// } -// } -// } -// } -// } -//} -// -//data class SuccessResponse( -// val id: Long, -// val name: String -//) -// -//data class FailRequest( -// val errorType: ErrorType, -// val message: String? = null -//) -// -//@RestController -//class ApiResponseTestController { -// -// @GetMapping("/success/{message}") -// fun succeedToGet( -// @PathVariable message: String, -// ): RoomescapeApiResponseKT = -// RoomescapeApiResponseKT.success(message) -// -// -// @PostMapping("/success/{id}/{name}") -// fun succeedToPost( -// @PathVariable id: Long, -// @PathVariable name: String, -// ): RoomescapeApiResponseKT = -// RoomescapeApiResponseKT.success(SuccessResponse(id, name)) -// -// -// @PostMapping("/fail") -// fun fail( -// @RequestBody request: FailRequest -// ): RoomescapeApiResponseKT = -// request.message?.let { -// RoomescapeApiResponseKT.fail(request.errorType, it) -// } ?: RoomescapeApiResponseKT.fail(request.errorType) -//} diff --git a/src/test/java/roomescape/member/controller/MemberControllerTest.kt b/src/test/java/roomescape/member/controller/MemberControllerTest.kt index 59e5b3a2..7f24f6d8 100644 --- a/src/test/java/roomescape/member/controller/MemberControllerTest.kt +++ b/src/test/java/roomescape/member/controller/MemberControllerTest.kt @@ -1,17 +1,21 @@ package roomescape.member.controller -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import io.kotest.assertions.assertSoftly import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.shouldBe import io.mockk.every -import io.restassured.module.kotlin.extensions.Extract -import org.hamcrest.Matchers.containsString +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.test.web.servlet.MockMvc +import roomescape.member.web.MemberController +import roomescape.member.web.MembersResponse import roomescape.util.MemberFixture import roomescape.util.RoomescapeApiTest -import roomescape.member.web.MembersResponse -class MemberControllerTest : RoomescapeApiTest() { +@WebMvcTest(controllers = [MemberController::class]) +class MemberControllerTest( + @Autowired private val mockMvc: MockMvc +) : RoomescapeApiTest() { init { given("GET /members 요청을") { @@ -24,16 +28,21 @@ class MemberControllerTest : RoomescapeApiTest() { ) `when`("관리자가 보내면") { - setUpAdmin() + loginAsAdmin() then("성공한다.") { - val result: Any = runGetTest(endpoint) { - statusCode(200) - } Extract { - path("data") - } + val result: String = runGetTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { isOk() } + }.andReturn().response.contentAsString - val response: MembersResponse = jacksonObjectMapper().convertValue(result, MembersResponse::class.java) + val response: MembersResponse = readValue( + responseJson = result, + valueType = MembersResponse::class.java + ) assertSoftly(response.members) { it.size shouldBe 3 @@ -44,20 +53,32 @@ class MemberControllerTest : RoomescapeApiTest() { `when`("관리자가 아니면 로그인 페이지로 이동한다.") { then("비회원") { - setUpNotLoggedIn() + doNotLogin() - runGetTest(endpoint) { - statusCode(200) - body(containsString("Login")) + runGetTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { is3xxRedirection() } + header { + string("Location", "/login") + } } } then("일반 회원") { - setUpUser() + loginAsUser() - runGetTest(endpoint) { - statusCode(200) - body(containsString("Login")) + runGetTest( + mockMvc = mockMvc, + endpoint = endpoint, + log = true + ) { + status { is3xxRedirection() } + header { + string("Location", "/login") + } } } } diff --git a/src/test/java/roomescape/util/Fixtures.kt b/src/test/java/roomescape/util/Fixtures.kt index d03b6fe5..4ae7a6b6 100644 --- a/src/test/java/roomescape/util/Fixtures.kt +++ b/src/test/java/roomescape/util/Fixtures.kt @@ -7,6 +7,8 @@ import roomescape.auth.web.LoginRequest import java.util.concurrent.atomic.AtomicLong object MemberFixture { + const val NOT_LOGGED_IN_USERID: Long = 0 + val idCounter: AtomicLong = AtomicLong(1L) fun create( diff --git a/src/test/java/roomescape/util/RoomescapeApiTest.kt b/src/test/java/roomescape/util/RoomescapeApiTest.kt index bd02bba7..23addfe6 100644 --- a/src/test/java/roomescape/util/RoomescapeApiTest.kt +++ b/src/test/java/roomescape/util/RoomescapeApiTest.kt @@ -1,30 +1,40 @@ package roomescape.util +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.ninjasquad.springmockk.MockkBean +import com.ninjasquad.springmockk.SpykBean import io.kotest.core.spec.style.BehaviorSpec import io.mockk.every -import io.restassured.http.ContentType -import io.restassured.module.kotlin.extensions.Given -import io.restassured.module.kotlin.extensions.Then -import io.restassured.module.kotlin.extensions.When -import io.restassured.response.ValidatableResponse -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.server.LocalServerPort import org.springframework.data.repository.findByIdOrNull +import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus -import roomescape.member.infrastructure.persistence.Member -import roomescape.member.infrastructure.persistence.MemberRepository +import org.springframework.http.MediaType +import org.springframework.test.web.servlet.* import roomescape.auth.infrastructure.jwt.JwtHandler +import roomescape.auth.web.support.AdminInterceptor +import roomescape.auth.web.support.LoginInterceptor +import roomescape.auth.web.support.MemberIdResolver import roomescape.common.exception.ErrorType import roomescape.common.exception.RoomescapeException +import roomescape.member.business.MemberService +import roomescape.member.infrastructure.persistence.Member +import roomescape.member.infrastructure.persistence.MemberRepository +import roomescape.util.MemberFixture.NOT_LOGGED_IN_USERID -const val NOT_LOGGED_IN_USERID: Long = 0; +abstract class RoomescapeApiTest : BehaviorSpec() { -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@NoSqlInitialize -class RoomescapeApiTest( - @LocalServerPort val port: Int? = 9090, -) : BehaviorSpec() { + @SpykBean + private lateinit var AdminInterceptor: AdminInterceptor + + @SpykBean + private lateinit var loginInterceptor: LoginInterceptor + + @SpykBean + private lateinit var memberIdResolver: MemberIdResolver + + @SpykBean + lateinit var memberService: MemberService @MockkBean lateinit var memberRepository: MemberRepository @@ -32,36 +42,42 @@ class RoomescapeApiTest( @MockkBean lateinit var jwtHandler: JwtHandler - + val objectMapper: ObjectMapper = jacksonObjectMapper() val admin: Member = MemberFixture.admin() val user: Member = MemberFixture.user() - fun runGetTest(endpoint: String, token: String? = "token", assert: ValidatableResponse.() -> Unit): ValidatableResponse { - return Given { - port(port!!) - header("Cookie", "accessToken=$token") - } When { - get(endpoint) - } Then assert + fun runGetTest( + mockMvc: MockMvc, + endpoint: String, + log: Boolean = false, + assert: MockMvcResultMatchersDsl.() -> Unit + ): ResultActionsDsl = mockMvc.get(endpoint) { + header(HttpHeaders.COOKIE, "accessToken=token") + }.apply { + log.takeIf { it }?.let { this.andDo { print() } } + }.andExpect { + assert } fun runPostTest( + mockMvc: MockMvc, endpoint: String, - token: String? = "token", body: Any? = null, - assert: ValidatableResponse.() -> Unit - ): ValidatableResponse { - return Given { - port(port!!) - contentType(ContentType.JSON) - body?.let { body(it) } - header("Cookie", "accessToken=$token") - } When { - post(endpoint) - } Then assert + log: Boolean = false, + assert: MockMvcResultMatchersDsl.() -> Unit + ): ResultActionsDsl = mockMvc.post(endpoint) { + this.header(HttpHeaders.COOKIE, "accessToken=token") + body?.let { + this.contentType = MediaType.APPLICATION_JSON + this.content = objectMapper.writeValueAsString(it) + } + }.apply { + log.takeIf { it }?.let { this.andDo { print() } } + }.andExpect { + assert } - fun setUpAdmin() { + fun loginAsAdmin() { every { jwtHandler.getMemberIdFromToken(any()) } returns admin.id!! @@ -70,7 +86,7 @@ class RoomescapeApiTest( every { memberRepository.findByIdOrNull(admin.id!!) } returns admin } - fun setUpUser() { + fun loginAsUser() { every { jwtHandler.getMemberIdFromToken(any()) } returns user.id!! @@ -79,15 +95,19 @@ class RoomescapeApiTest( every { memberRepository.findByIdOrNull(user.id!!) } returns user } - fun setUpNotLoggedIn() { + fun doNotLogin() { every { jwtHandler.getMemberIdFromToken(any()) - } returns NOT_LOGGED_IN_USERID + } throws RoomescapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED) - every { memberRepository.existsById(NOT_LOGGED_IN_USERID) } throws RoomescapeException( - ErrorType.LOGIN_REQUIRED, - HttpStatus.FORBIDDEN - ) + every { memberRepository.existsById(NOT_LOGGED_IN_USERID) } returns false every { memberRepository.findByIdOrNull(NOT_LOGGED_IN_USERID) } returns null } + + fun readValue(responseJson: String, valueType: Class): T = objectMapper + .readTree(responseJson)["data"] + ?.let { objectMapper.convertValue(it, valueType) } + ?: throw RuntimeException(""" + [Test] Exception occurred while reading response json: $responseJson with value type: $valueType + """.trimIndent()) } diff --git a/src/test/java/roomescape/util/TestAnnotations.kt b/src/test/java/roomescape/util/TestAnnotations.kt deleted file mode 100644 index 0d04e884..00000000 --- a/src/test/java/roomescape/util/TestAnnotations.kt +++ /dev/null @@ -1,11 +0,0 @@ -package roomescape.util - -import org.springframework.test.context.TestPropertySource - -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -@TestPropertySource(properties = [ - "spring.jpa.hibernate.ddl-auto=none", - "spring.sql.init.mode=never" -]) -annotation class NoSqlInitialize diff --git a/src/test/java/roomescape/view/PageControllerTest.kt b/src/test/java/roomescape/view/PageControllerTest.kt index 273d9235..a9f10075 100644 --- a/src/test/java/roomescape/view/PageControllerTest.kt +++ b/src/test/java/roomescape/view/PageControllerTest.kt @@ -1,35 +1,56 @@ package roomescape.view -import org.hamcrest.Matchers +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest +import org.springframework.test.web.servlet.MockMvc import roomescape.util.RoomescapeApiTest -class PageControllerTest() : RoomescapeApiTest() { +@WebMvcTest(controllers = [ + AuthPageController::class, + AdminPageController::class, + ClientPageController::class +]) +class PageControllerTest( + @Autowired private val mockMvc: MockMvc +) : RoomescapeApiTest() { init { listOf("/", "/login").forEach { given("GET $it 요청은") { `when`("로그인 및 권한 여부와 관계없이 성공한다.") { then("비회원") { - setUpNotLoggedIn() + doNotLogin() - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } then("회원") { - setUpUser() + loginAsUser() - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } then("관리자") { - setUpAdmin() + loginAsAdmin() - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } } @@ -39,22 +60,32 @@ class PageControllerTest() : RoomescapeApiTest() { listOf("/admin", "/admin/reservation", "/admin/time", "/admin/theme", "/admin/waiting").forEach { given("GET $it 요청을") { `when`("관리자가 보내면") { - setUpAdmin() + loginAsAdmin() then("성공한다.") { - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } } `when`("회원이 보내면") { - setUpUser() + loginAsUser() then("로그인 페이지로 이동한다.") { - runGetTest(it) { - statusCode(200) - body(Matchers.containsString("Login")) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { is3xxRedirection() } + header { + string("Location", "/login") + } } } } @@ -65,28 +96,42 @@ class PageControllerTest() : RoomescapeApiTest() { given("GET $it 요청을") { `when`("로그인 된 회원이 보내면 성공한다.") { then("회원") { - setUpUser() + loginAsUser() - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } then("관리자") { - setUpAdmin() + loginAsAdmin() - runGetTest(it) { - statusCode(200) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { isOk() } } } } `when`("로그인 없이 보내면") { then("로그인 페이지로 이동한다.") { - setUpNotLoggedIn() + doNotLogin() - runGetTest(it) { - statusCode(200) - body(Matchers.containsString("Login")) + runGetTest( + mockMvc = mockMvc, + endpoint = it, + log = true + ) { + status { is3xxRedirection() } + header { + string("Location", "/login") + } } } }