From 0b0891be26e9dbd66586eb4aaba0f1ac84d24a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A4=80=ED=97=8C?= Date: Wed, 26 Feb 2025 13:10:45 +0900 Subject: [PATCH 1/5] Add AbstractControllerTest for shared test utilities Introduce an abstract base test class to centralize and simplify common test setup for controller tests. This includes MockMvc auto-configuration to streamline testing in Spring WebMvcTest contexts. --- .../springboot/shared/AbstractControllerTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 spring-boot/src/test/java/com/mpc/springboot/shared/AbstractControllerTest.java diff --git a/spring-boot/src/test/java/com/mpc/springboot/shared/AbstractControllerTest.java b/spring-boot/src/test/java/com/mpc/springboot/shared/AbstractControllerTest.java new file mode 100644 index 0000000..1397490 --- /dev/null +++ b/spring-boot/src/test/java/com/mpc/springboot/shared/AbstractControllerTest.java @@ -0,0 +1,14 @@ +package com.mpc.springboot.shared; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest +@AutoConfigureMockMvc +public abstract class AbstractControllerTest { + + @Autowired + protected MockMvc mockMvc; +} From afa32673a668b5f56aa58a2e83b43067fbeec2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A4=80=ED=97=8C?= Date: Wed, 26 Feb 2025 13:41:15 +0900 Subject: [PATCH 2/5] Refine headers in week 3 study plan summary. Removed redundant numbering in headers for clarity and improved the phrasing to better emphasize the necessity of DTO classes in both testing and data transmission contexts. This ensures better readability and consistency across the document. --- plan/4.3. study_plan-week-3-summary.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plan/4.3. study_plan-week-3-summary.md b/plan/4.3. study_plan-week-3-summary.md index 8771a72..74f2f1d 100644 --- a/plan/4.3. study_plan-week-3-summary.md +++ b/plan/4.3. study_plan-week-3-summary.md @@ -8,12 +8,12 @@ - **3.1.4.** ManyToOne, OneToMany 사용 - **3.2.** Spring Boot 테스트 코드 작성 - **3.2.1.** 단위 테스트 (Unit Test) 작성 - - **3.2.1.1.** 테스트 관점에서의 Request, Response DTO 클래스 작성의 필요성 + - 테스트 관점에서의 Request, Response DTO 클래스 작성의 필요성 - **3.2.2.** 통합 테스트 (Integration Test) 작성 - **3.2.3.** MockMvc 사용 - **3.3.** RESTful API 설계 및 구현 - **3.3.1.** RESTful API 기본 원칙 - - **3.3.1.1.** 데이터 전송 관점에서의 Request, Response DTO 클래스 작성 + - 데이터 전송 관점에서의 Request, Response DTO 클래스 작성의 필요성 - **3.3.3.** API 예외 처리 - **3.3.4.** Spring Validation 사용 - **3.3.5.** Converter 사용 From cafa85e95732e684fba7de6eb88039b98ebfb384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A4=80=ED=97=8C?= Date: Wed, 26 Feb 2025 13:48:34 +0900 Subject: [PATCH 3/5] Refactor MemberService to return DTO in getMemberBy. Updated `MemberService` to return `MemberResponse` DTO instead of the `Member` entity, encapsulating domain data. Introduced the `MemberResponse` class to map entity fields, and adjusted the controller to reflect the DTO usage. This improves separation of concerns and avoids exposing domain entities directly. --- .../member/application/dto/MemberResponse.java | 18 ++++++++++++++++++ .../application/service/MemberService.java | 8 +++++--- .../controller/MemberRestController.java | 3 ++- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java diff --git a/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java b/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java new file mode 100644 index 0000000..4e1d50b --- /dev/null +++ b/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java @@ -0,0 +1,18 @@ +package com.mpc.springboot.member.application.dto; + +import com.mpc.springboot.member.domain.entity.Member; +import com.mpc.springboot.member.domain.vo.MemberCode; +import com.mpc.springboot.member.domain.vo.MemberName; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class MemberResponse { + private final MemberCode code; + private final MemberName name; + + public static MemberResponse of(Member member) { + return new MemberResponse(member.getCode(), member.getName()); + } +} diff --git a/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java b/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java index 948efae..1c9cfb1 100644 --- a/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java +++ b/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java @@ -3,6 +3,7 @@ import jakarta.transaction.Transactional; import org.springframework.stereotype.Service; +import com.mpc.springboot.member.application.dto.MemberResponse; import com.mpc.springboot.member.domain.entity.Member; import com.mpc.springboot.member.domain.exception.MemberNotFoundException; import com.mpc.springboot.member.domain.repository.MemberRepository; @@ -15,9 +16,10 @@ public class MemberService { private final MemberRepository memberRepository; - public Member getMemberBy(MemberCode code) { - return memberRepository.findMemberBy(code) - .orElseThrow(MemberNotFoundException::new); + public MemberResponse getMemberBy(MemberCode code) { + Member member = memberRepository.findMemberBy(code) + .orElseThrow(MemberNotFoundException::new); + return MemberResponse.of(member); } @Transactional diff --git a/spring-boot/src/main/java/com/mpc/springboot/member/presentation/controller/MemberRestController.java b/spring-boot/src/main/java/com/mpc/springboot/member/presentation/controller/MemberRestController.java index 3bce35d..2cab449 100644 --- a/spring-boot/src/main/java/com/mpc/springboot/member/presentation/controller/MemberRestController.java +++ b/spring-boot/src/main/java/com/mpc/springboot/member/presentation/controller/MemberRestController.java @@ -2,6 +2,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import com.mpc.springboot.member.application.dto.MemberResponse; import com.mpc.springboot.member.application.service.MemberService; import com.mpc.springboot.member.domain.entity.Member; import com.mpc.springboot.member.domain.vo.MemberCode; @@ -15,7 +16,7 @@ public class MemberRestController { private final MemberService memberService; @GetMapping("/{code}") - public ResponseEntity getMemberBy(@PathVariable MemberCode code) { + public ResponseEntity getMemberBy(@PathVariable MemberCode code) { return ResponseEntity.ok(memberService.getMemberBy(code)); } From bbc5826f682be60618d12d895973cb76c4c4fa23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A4=80=ED=97=8C?= Date: Wed, 26 Feb 2025 14:22:11 +0900 Subject: [PATCH 4/5] Enable JPA auditing and add member-related improvements Moved @EnableJpaAuditing to a dedicated configuration class for clarity. Updated the `MemberResponse` class to use a `from` method with protected access for the constructor. Added unit tests for `MemberRestController` to validate member retrieval by code. --- .../java/com/mpc/springboot/Application.java | 1 - .../config/audit/AuditingConfiguration.java | 9 ++++ .../application/dto/MemberResponse.java | 7 ++- .../application/service/MemberService.java | 2 +- .../controller/MemberRestControllerTest.java | 53 +++++++++++++++++++ 5 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 spring-boot/src/main/java/com/mpc/springboot/config/audit/AuditingConfiguration.java create mode 100644 spring-boot/src/test/java/com/mpc/springboot/member/presentation/controller/MemberRestControllerTest.java diff --git a/spring-boot/src/main/java/com/mpc/springboot/Application.java b/spring-boot/src/main/java/com/mpc/springboot/Application.java index 51b0be6..99cf2ca 100644 --- a/spring-boot/src/main/java/com/mpc/springboot/Application.java +++ b/spring-boot/src/main/java/com/mpc/springboot/Application.java @@ -4,7 +4,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; -@EnableJpaAuditing @SpringBootApplication public class Application { diff --git a/spring-boot/src/main/java/com/mpc/springboot/config/audit/AuditingConfiguration.java b/spring-boot/src/main/java/com/mpc/springboot/config/audit/AuditingConfiguration.java new file mode 100644 index 0000000..7277ac0 --- /dev/null +++ b/spring-boot/src/main/java/com/mpc/springboot/config/audit/AuditingConfiguration.java @@ -0,0 +1,9 @@ +package com.mpc.springboot.config.audit; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing +@Configuration +public class AuditingConfiguration { +} diff --git a/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java b/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java index 4e1d50b..b6af37d 100644 --- a/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java +++ b/spring-boot/src/main/java/com/mpc/springboot/member/application/dto/MemberResponse.java @@ -3,16 +3,15 @@ import com.mpc.springboot.member.domain.entity.Member; import com.mpc.springboot.member.domain.vo.MemberCode; import com.mpc.springboot.member.domain.vo.MemberName; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.*; @Getter -@RequiredArgsConstructor +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class MemberResponse { private final MemberCode code; private final MemberName name; - public static MemberResponse of(Member member) { + public static MemberResponse from(Member member) { return new MemberResponse(member.getCode(), member.getName()); } } diff --git a/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java b/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java index 1c9cfb1..d720de7 100644 --- a/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java +++ b/spring-boot/src/main/java/com/mpc/springboot/member/application/service/MemberService.java @@ -19,7 +19,7 @@ public class MemberService { public MemberResponse getMemberBy(MemberCode code) { Member member = memberRepository.findMemberBy(code) .orElseThrow(MemberNotFoundException::new); - return MemberResponse.of(member); + return MemberResponse.from(member); } @Transactional diff --git a/spring-boot/src/test/java/com/mpc/springboot/member/presentation/controller/MemberRestControllerTest.java b/spring-boot/src/test/java/com/mpc/springboot/member/presentation/controller/MemberRestControllerTest.java new file mode 100644 index 0000000..87bd87e --- /dev/null +++ b/spring-boot/src/test/java/com/mpc/springboot/member/presentation/controller/MemberRestControllerTest.java @@ -0,0 +1,53 @@ +package com.mpc.springboot.member.presentation.controller; + +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import com.mpc.springboot.member.application.dto.MemberResponse; +import com.mpc.springboot.member.application.service.MemberService; +import com.mpc.springboot.member.domain.entity.Member; +import com.mpc.springboot.member.domain.repository.MemberRepository; +import com.mpc.springboot.member.domain.vo.MemberCode; +import com.mpc.springboot.member.domain.vo.MemberName; +import com.mpc.springboot.shared.AbstractControllerTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(MemberRestController.class) +class MemberRestControllerTest extends AbstractControllerTest { + + @MockitoBean + private MemberService memberService; + + /** + * @see MemberRestController#getMemberBy(MemberCode) + */ + @DisplayName("회원 조회: 회원 코드로 조회") + @Test + void getMemberBy() throws Exception { + // given + MemberCode code = MemberCode.of("M0000003"); + MemberName name = MemberName.of("John", "Doe"); + Member member = Member.of(code, name); + MemberResponse memberResponse = MemberResponse.from(member); + given(memberService.getMemberBy(any())).willReturn(memberResponse); + + // when & then + mockMvc.perform(get("/api/v1/members/{code}", code.getValue())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code.value").value(code.getValue())) + .andExpect(jsonPath("$.name.firstName").value(name.getFirstName())) + .andExpect(jsonPath("$.name.lastName").value(name.getLastName())) + .andExpect(jsonPath("$.name.fullName").value(name.getFullName())); + + } + + @Test + void createMember() { + } +} \ No newline at end of file From d45c31e652eb0b901877e6293d9c3cffd6289f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=95=88=EC=A4=80=ED=97=8C?= Date: Wed, 26 Feb 2025 14:24:20 +0900 Subject: [PATCH 5/5] Move MockMvc usage under unit testing section Reorganized the MockMvc usage bullet point to align it with unit testing instead of integration testing. This improves clarity and maintains consistency in the structure of the study plan. --- plan/4.3. study_plan-week-3-summary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plan/4.3. study_plan-week-3-summary.md b/plan/4.3. study_plan-week-3-summary.md index 74f2f1d..ce83b7d 100644 --- a/plan/4.3. study_plan-week-3-summary.md +++ b/plan/4.3. study_plan-week-3-summary.md @@ -8,9 +8,9 @@ - **3.1.4.** ManyToOne, OneToMany 사용 - **3.2.** Spring Boot 테스트 코드 작성 - **3.2.1.** 단위 테스트 (Unit Test) 작성 + - MockMvc 사용 - 테스트 관점에서의 Request, Response DTO 클래스 작성의 필요성 - **3.2.2.** 통합 테스트 (Integration Test) 작성 - - **3.2.3.** MockMvc 사용 - **3.3.** RESTful API 설계 및 구현 - **3.3.1.** RESTful API 기본 원칙 - 데이터 전송 관점에서의 Request, Response DTO 클래스 작성의 필요성