From 48eecda7a71a330b9fc5b933b2e0aa986cfcf752 Mon Sep 17 00:00:00 2001 From: qkr10 Date: Tue, 22 Jul 2025 15:26:11 +0900 Subject: [PATCH 1/2] =?UTF-8?q?test:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20api?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/api/common/dto/ApiResponse.java | 8 +- .../server/api/common/dto/CommonResponse.java | 9 +- .../api/user/mypage/dto/MyInfoResponse.java | 2 + .../controller/CommonAuthControllerTest.java | 101 ++++++++++++++++++ .../backend/server/config/ControllerTest.java | 7 +- ...kMvcUTF8Config.java => MockMvcConfig.java} | 5 +- .../backend/server/fixture/UserFixture.java | 10 ++ 7 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java rename src/test/java/com/backend/server/config/{MockMvcUTF8Config.java => MockMvcConfig.java} (80%) diff --git a/src/main/java/com/backend/server/api/common/dto/ApiResponse.java b/src/main/java/com/backend/server/api/common/dto/ApiResponse.java index 51375ee4..8cd3b0ee 100644 --- a/src/main/java/com/backend/server/api/common/dto/ApiResponse.java +++ b/src/main/java/com/backend/server/api/common/dto/ApiResponse.java @@ -2,13 +2,15 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @AllArgsConstructor +@NoArgsConstructor public class ApiResponse { - private final String status; - private final String message; - private final T data; + private String status; + private String message; + private T data; public static ApiResponse success(String message, T data) { return new ApiResponse<>("success", message, data); diff --git a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java index e3ee3752..37df8f3d 100644 --- a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java +++ b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java @@ -2,13 +2,16 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Getter @AllArgsConstructor +@NoArgsConstructor public class CommonResponse { - private final String status; - private final String message; - private final T data; + private String status; + private String message; + private T data; public static CommonResponse success(String message, T data) { return new CommonResponse<>("success", message, data); diff --git a/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java b/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java index f0c5d654..3bd8ee69 100644 --- a/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java +++ b/src/main/java/com/backend/server/api/user/mypage/dto/MyInfoResponse.java @@ -3,8 +3,10 @@ import com.backend.server.api.common.dto.LoginUser; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@NoArgsConstructor public class MyInfoResponse { @Schema(description = "사용자 이름", example = "홍길동") diff --git a/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java new file mode 100644 index 00000000..2b9e52c9 --- /dev/null +++ b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java @@ -0,0 +1,101 @@ +package com.backend.server.api.common.auth.controller; + +import static com.backend.server.fixture.UserFixture.MOCK_MVC_테스트시_로그인_계정; +import static com.backend.server.util.MockMvcUtil.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.backend.server.api.common.auth.dto.CommonSignInRequest; +import com.backend.server.api.common.auth.dto.CommonSignInResponse; +import com.backend.server.api.common.dto.ApiResponse; +import com.backend.server.api.common.dto.CommonResponse; +import com.backend.server.api.common.dto.LoginUser; +import com.backend.server.api.user.mypage.dto.MyInfoResponse; +import com.backend.server.config.MockMvcConfig; +import com.backend.server.model.entity.User; +import com.backend.server.model.repository.user.UserRepository; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.Cookie; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +@Transactional +@SpringBootTest +@AutoConfigureMockMvc +@Import(MockMvcConfig.class) +@DisplayName("CommonAuthController") +class CommonAuthControllerTest { + + @Autowired private MockMvc mockMvc; + @Autowired private PasswordEncoder passwordEncoder; + @Autowired private ObjectMapper objectMapper; + @Autowired private UserRepository userRepository; + + @Nested + class 로그인_API_는 { + + @Test + void 사용가능한_엑세스_토큰을_응답한다() throws Exception { + /* 1. 로그인 */ + //given + final User user = userRepository.save(MOCK_MVC_테스트시_로그인_계정.엔티티_생성(passwordEncoder, null)); + userRepository.flush(); + final CommonSignInRequest signInRequest = MOCK_MVC_테스트시_로그인_계정.로그인_요청_생성(); + + //when + final ResultActions resultActions = mockMvc.perform(post("/api/auth/sign-in") + .contentType(MediaType.APPLICATION_JSON) + .content(convertToJson(signInRequest))); + + //then + resultActions.andExpect(status().isOk()); + + final Cookie refreshToken = resultActions.andReturn().getResponse().getCookie("refreshToken"); + if (refreshToken == null) + throw new Exception("No refresh token found"); + System.out.printf("refresh token : %s\n", refreshToken.getValue()); + + final String responseJson = resultActions.andReturn().getResponse().getContentAsString(); + final JavaType type = objectMapper.getTypeFactory() + .constructParametricType(CommonResponse.class, CommonSignInResponse.class); + final CommonResponse signInResponse = objectMapper.readValue(responseJson, type); + final String accessToken = signInResponse.getData().getAccessToken(); + System.out.printf("access token : %s\n", accessToken); + /* 1. 로그인 */ + + /* 2. 발급 받은 엑세스 토큰으로 내 정보 조회하기 */ + //given + final MyInfoResponse expectedMyInfoResponse = new MyInfoResponse(new LoginUser(user)); + + //when + final ResultActions resultActions2 = mockMvc.perform(get("/api/mypage") + .header("Referer", "http://localhost:3000") + .header("Authorization", "Bearer " + accessToken)); + + //then + resultActions2.andExpect(status().isOk()); + + final String responseJson2 = resultActions2.andReturn().getResponse().getContentAsString(); + final JavaType type2 = objectMapper.getTypeFactory() + .constructParametricType(ApiResponse.class, MyInfoResponse.class); + final ApiResponse myInfoResponse = objectMapper.readValue(responseJson2, type2); + assertThat(myInfoResponse.getData()) + .usingRecursiveComparison() + .isEqualTo(expectedMyInfoResponse) + .as("내 정보 조회 api 가 예상과 다른 값을 반환합니다."); + /* 2. 발급 받은 엑세스 토큰으로 내 정보 조회하기 */ + } + } +} \ No newline at end of file diff --git a/src/test/java/com/backend/server/config/ControllerTest.java b/src/test/java/com/backend/server/config/ControllerTest.java index a009ebbc..d2c2746b 100644 --- a/src/test/java/com/backend/server/config/ControllerTest.java +++ b/src/test/java/com/backend/server/config/ControllerTest.java @@ -12,6 +12,11 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestExecutionListeners; +/** + * 로그인을 자동으로 해주어, 컨트롤러 레이어의 테스트를 쉽게 할수 있게 해주는 어노테이션입니다.
+ * dev 서버용 인증필터를 사용합니다.
+ * (엑세스 토큰이 없으면, 알아서 액세스 토큰을 만들어서 인증시켜줌) + */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Inherited @@ -19,7 +24,7 @@ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("dev") -@Import(MockMvcUTF8Config.class) +@Import(MockMvcConfig.class) @TestExecutionListeners( value = UserSaveListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) diff --git a/src/test/java/com/backend/server/config/MockMvcUTF8Config.java b/src/test/java/com/backend/server/config/MockMvcConfig.java similarity index 80% rename from src/test/java/com/backend/server/config/MockMvcUTF8Config.java rename to src/test/java/com/backend/server/config/MockMvcConfig.java index 0291124b..79293440 100644 --- a/src/test/java/com/backend/server/config/MockMvcUTF8Config.java +++ b/src/test/java/com/backend/server/config/MockMvcConfig.java @@ -1,5 +1,7 @@ package com.backend.server.config; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -9,12 +11,13 @@ import org.springframework.web.filter.CharacterEncodingFilter; @TestConfiguration -public class MockMvcUTF8Config { +public class MockMvcConfig { @Bean @Primary public MockMvc customMockMvc(WebApplicationContext context) { return MockMvcBuilders.webAppContextSetup(context) .addFilter(new CharacterEncodingFilter("UTF-8", true)) + .apply(springSecurity()) .build(); } } diff --git a/src/test/java/com/backend/server/fixture/UserFixture.java b/src/test/java/com/backend/server/fixture/UserFixture.java index 29e703f1..b0f66f8c 100644 --- a/src/test/java/com/backend/server/fixture/UserFixture.java +++ b/src/test/java/com/backend/server/fixture/UserFixture.java @@ -1,6 +1,9 @@ package com.backend.server.fixture; import com.backend.server.api.admin.user.dto.AdminUserRequest; +import com.backend.server.api.common.auth.dto.CommonSignInRequest; +import com.backend.server.api.common.dto.LoginUser; +import com.backend.server.api.user.mypage.dto.MyInfoResponse; import com.backend.server.model.entity.Professor; import com.backend.server.model.entity.User; import com.backend.server.model.entity.enums.Gender; @@ -130,4 +133,11 @@ public enum UserFixture { .birthday(birthDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))) .build(); } + + public CommonSignInRequest 로그인_요청_생성() { + final CommonSignInRequest commonSignInRequest = new CommonSignInRequest(); + commonSignInRequest.setPassword(password); + commonSignInRequest.setStudentNumber(studentNumber); + return commonSignInRequest; + } } From 922e525af45abdd14680980c517db7941bbe8d61 Mon Sep 17 00:00:00 2001 From: qkr10 Date: Tue, 22 Jul 2025 16:07:03 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/api/common/dto/CommonResponse.java | 1 - .../controller/CommonAuthControllerTest.java | 24 +++++++------------ .../CommonPresignedUrlControllerTest.java | 5 ++-- .../backend/server/fixture/UserFixture.java | 2 -- .../com/backend/server/util/MockMvcUtil.java | 18 ++++++++++++-- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java index 37df8f3d..a1536503 100644 --- a/src/main/java/com/backend/server/api/common/dto/CommonResponse.java +++ b/src/main/java/com/backend/server/api/common/dto/CommonResponse.java @@ -3,7 +3,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; @Getter @AllArgsConstructor diff --git a/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java index 2b9e52c9..18475893 100644 --- a/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java +++ b/src/test/java/com/backend/server/api/common/auth/controller/CommonAuthControllerTest.java @@ -1,22 +1,22 @@ package com.backend.server.api.common.auth.controller; import static com.backend.server.fixture.UserFixture.MOCK_MVC_테스트시_로그인_계정; -import static com.backend.server.util.MockMvcUtil.*; +import static com.backend.server.util.MockMvcUtil.convertToJson; +import static com.backend.server.util.MockMvcUtil.toDto; +import static com.backend.server.util.MockMvcUtil.toJsonPathDocument; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.backend.server.api.common.auth.dto.CommonSignInRequest; -import com.backend.server.api.common.auth.dto.CommonSignInResponse; import com.backend.server.api.common.dto.ApiResponse; -import com.backend.server.api.common.dto.CommonResponse; import com.backend.server.api.common.dto.LoginUser; import com.backend.server.api.user.mypage.dto.MyInfoResponse; import com.backend.server.config.MockMvcConfig; import com.backend.server.model.entity.User; import com.backend.server.model.repository.user.UserRepository; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.type.TypeReference; import jakarta.servlet.http.Cookie; import jakarta.transaction.Transactional; import org.junit.jupiter.api.DisplayName; @@ -40,7 +40,6 @@ class CommonAuthControllerTest { @Autowired private MockMvc mockMvc; @Autowired private PasswordEncoder passwordEncoder; - @Autowired private ObjectMapper objectMapper; @Autowired private UserRepository userRepository; @Nested @@ -67,11 +66,7 @@ class 로그인_API_는 { throw new Exception("No refresh token found"); System.out.printf("refresh token : %s\n", refreshToken.getValue()); - final String responseJson = resultActions.andReturn().getResponse().getContentAsString(); - final JavaType type = objectMapper.getTypeFactory() - .constructParametricType(CommonResponse.class, CommonSignInResponse.class); - final CommonResponse signInResponse = objectMapper.readValue(responseJson, type); - final String accessToken = signInResponse.getData().getAccessToken(); + final String accessToken = toJsonPathDocument(resultActions).read("$.data.accessToken"); System.out.printf("access token : %s\n", accessToken); /* 1. 로그인 */ @@ -87,10 +82,7 @@ class 로그인_API_는 { //then resultActions2.andExpect(status().isOk()); - final String responseJson2 = resultActions2.andReturn().getResponse().getContentAsString(); - final JavaType type2 = objectMapper.getTypeFactory() - .constructParametricType(ApiResponse.class, MyInfoResponse.class); - final ApiResponse myInfoResponse = objectMapper.readValue(responseJson2, type2); + final ApiResponse myInfoResponse = toDto(resultActions2, new TypeReference<>() {}); assertThat(myInfoResponse.getData()) .usingRecursiveComparison() .isEqualTo(expectedMyInfoResponse) diff --git a/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java b/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java index 6d72aa02..bd0861ce 100644 --- a/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java +++ b/src/test/java/com/backend/server/api/common/s3/controller/CommonPresignedUrlControllerTest.java @@ -1,12 +1,12 @@ package com.backend.server.api.common.s3.controller; +import static com.backend.server.util.MockMvcUtil.toJsonPathDocument; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.backend.server.config.ControllerTest; import com.backend.server.util.S3ApiUtil; -import com.jayway.jsonpath.JsonPath; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -44,8 +44,7 @@ class Presigned_URL_API_는 { //then result.andExpect(status().isOk()); - String responseJson = result.andReturn().getResponse().getContentAsString(); - String presignedUrl = JsonPath.parse(responseJson).read("$.data", String.class); + String presignedUrl = toJsonPathDocument(result).read("$.data", String.class); /* presigned url 을 발급받습니다. */ /* presigned url 로 파일이 잘 업로드 되는지 테스트 */ diff --git a/src/test/java/com/backend/server/fixture/UserFixture.java b/src/test/java/com/backend/server/fixture/UserFixture.java index b0f66f8c..1a931968 100644 --- a/src/test/java/com/backend/server/fixture/UserFixture.java +++ b/src/test/java/com/backend/server/fixture/UserFixture.java @@ -2,8 +2,6 @@ import com.backend.server.api.admin.user.dto.AdminUserRequest; import com.backend.server.api.common.auth.dto.CommonSignInRequest; -import com.backend.server.api.common.dto.LoginUser; -import com.backend.server.api.user.mypage.dto.MyInfoResponse; import com.backend.server.model.entity.Professor; import com.backend.server.model.entity.User; import com.backend.server.model.entity.enums.Gender; diff --git a/src/test/java/com/backend/server/util/MockMvcUtil.java b/src/test/java/com/backend/server/util/MockMvcUtil.java index 6dbc83c7..96adc633 100644 --- a/src/test/java/com/backend/server/util/MockMvcUtil.java +++ b/src/test/java/com/backend/server/util/MockMvcUtil.java @@ -6,12 +6,16 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; +import java.io.UnsupportedEncodingException; import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.util.LinkedMultiValueMap; @@ -24,8 +28,8 @@ public class MockMvcUtil { .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); /** - * 하나의 API 응답에 대해 여러번 이 함수를 호출할 일이 있다면, JsonPath 라이브러리로 대체하는걸 추천합니다. - * 이 메소드는 API 응답을 json 으로 파싱하므로, 한번의 API 응답에 대해 여러번 호출하기 부적절합니다. + * 이 메소드는 API 응답을 json 으로 파싱하므로, 한번의 API 응답에 대해 여러번 호출하기 부적절합니다.
+ * 여러번 호출할거라면, toJsonPathDocument() / toDto 를 사용하세요. */ public static ResultMatcher jsonPathEquals(String jsonPath, Object expected) { return result -> { @@ -66,6 +70,16 @@ public static MultiValueMap convertToParams(Object dto) { return params; } + public static DocumentContext toJsonPathDocument(ResultActions result) throws UnsupportedEncodingException { + String responseBody = result.andReturn().getResponse().getContentAsString(); + return JsonPath.parse(responseBody); + } + + public static T toDto(ResultActions result, TypeReference typeReference) throws Exception { + String responseBody = result.andReturn().getResponse().getContentAsString(); + return mapper.readValue(responseBody, typeReference); + } + public static MockHttpServletRequestBuilder postJson(String url, Object body) throws JsonProcessingException { return post(url) .contentType(MediaType.APPLICATION_JSON)