본문 바로가기
Project/깃터디 (gitudy)

[깃터디/Convention] 컨벤션 수정 api 구현

by J-rain 2024. 5. 11.

 

💡 .java를 클릭시 관련 커밋으로 이동💡

 

 회고 

    @Test
    @DisplayName("컨벤션 수정 테스트")
    public void updateStudyConvention() {
        //given
        User savedUser = userRepository.save(generateAuthUser());

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
        studyInfoRepository.save(studyInfo);

        StudyMember leader = StudyMemberFixture.createStudyMemberLeader(savedUser.getId(), studyInfo.getId());
        studyMemberRepository.save(leader);

        StudyConvention studyConvention = StudyConventionFixture.createStudyDefaultConvention(studyInfo.getId());
        studyConventionRepository.save(studyConvention);

        StudyConventionUpdateRequest updateRequest = StudyConventionFixture.generateStudyConventionUpdateRequest();

        //when
        studyMemberService.isValidateStudyLeader(savedUser, studyInfo.getId());
        studyConventionService.updateStudyConvention(updateRequest, studyConvention.getId());
        StudyConvention updateConvention = studyConventionRepository.findById(studyConvention.getId())
                .orElseThrow(() -> new ConventionException(ExceptionMessage.CONVENTION_NOT_FOUND));

        //then
        assertEquals("컨벤션 수정", updateConvention.getName());
        assertEquals("설명 수정", updateConvention.getDescription());
        assertEquals("정규식 수정", updateConvention.getContent());
    }

컨벤션 수정을 테스트 해보는 과정에서 assertEquals("컨벤션 수정", updateConvention.getName()); 이부분을 → assertEquals("컨벤션 수정", studyConvention.getName()); 으로 변경하면 테스트가 실패해서 궁금했는데 그 이유가

JPA(Java Persistence API)의 동작 방식과 관련이 있었다. JPA를 사용할 때, 엔티티의 상태는 영속성 컨텍스트에 의해 관리되고 영속성 컨텍스트는 엔티티의 생명 주기를 관리하며, 데이터베이스와 애플리케이션 사이의 버퍼 역할을 하게 된다. 그래서

studyConvention.getName()으로 사용했을 때 값이 변경되지 않는 이유가

studyConvention 엔티티 인스턴스는 updateStudyConvention 메소드를 실행하기 전에 영속성 컨텍스트에 저장된다. 이 인스턴스는 데이터베이스에 저장된 원본 값을 가지고 있으며, updateStudyConvention 메소드 내에서 이 인스턴스의 상태가 변경되어도, 그 변경 사항은 즉시 studyConvention 인스턴스에 반영되지 않는다. 왜냐하면, 실제 엔티티의 상태 변화는 데이터베이스에 커밋될 때만 영속성 컨텍스트와 동기화된다. 따라서, 변경된 데이터를 확인하려면, 데이터베이스에서 새롭게 업데이트된 엔티티를 다시 조회해야 한다는 것(StudyConvention updateConvention = studyConventionRepository.findById(studyConvention.getId()) ) 을 깨달았다… 👍

 

 

StudyConventionController.java

    @ApiResponse(responseCode = "200", description = "컨벤션 수정 성공")
    @PutMapping("/{studyInfoId}/convention/{conventionId}")
    public JsonResult<?> updateStudyConvention(@AuthenticationPrincipal User user,
                                               @PathVariable(name = "studyInfoId") Long studyInfoId,
                                               @PathVariable(name = "conventionId") Long conventionId,
                                               @Valid @RequestBody StudyConventionUpdateRequest studyConventionUpdateRequest) {

        studyMemberService.isValidateStudyLeader(user, studyInfoId);

        studyConventionService.updateStudyConvention(studyConventionUpdateRequest, conventionId);

        return JsonResult.successOf("StudyConvention update Success");
    }

컨벤션 수정 컨트롤러 구현 @PathVariable 를 통하여 studyInfoId와 conventionId를 받도록 구현했다.

 

 

StudyConventionUpdateRequest.java

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StudyConventionUpdateRequest {

    @NotBlank(message = "컨벤션 이름은 공백일 수 없습니다.")
    @Size(max = 20, message = "이름 20자 이내")
    private String name;   // 컨벤션 이름

    @Size(max = 50, message = "설명 50자 이내")
    private String description; // 컨벤션 설명

    @NotBlank(message = "컨벤션 내용은 공백일 수 없습니다.")
    @Size(max = 40, message = "내용 40자 이내")
    private String content;  // 컨벤션 내용(정규식)

    @Builder.Default
    private boolean active = true; // 컨벤션 적용 여부
}

컨벤션 수정 DTO

 

 

StudyConventionService.java

   // 컨벤션 수정
    @Transactional
    public void updateStudyConvention(StudyConventionUpdateRequest request, Long conventionId) {

        // Convention 조회
        StudyConvention studyConvention = studyConventionRepository.findById(conventionId).orElseThrow(() -> {
            log.warn(">>>> {} : {} <<<<", conventionId, ExceptionMessage.CONVENTION_NOT_FOUND.getText());
            return new ConventionException(ExceptionMessage.CONVENTION_NOT_FOUND);
        });

        // 기존 Convention 업데이트
        studyConvention.updateConvention(
                request.getName(),
                request.getDescription(),
                request.getContent(),
                request.isActive());
    }

컨벤션 수정 서비스 먼저 가져온 conventionId로 Repository에서 조회한뒤 없으면 예외처리 해주도록 설계했다.

 


테스트

 

StudyConventionFixture.java

    // 테스트용 컨벤션 수정
    public static StudyConventionUpdateRequest generateStudyConventionUpdateRequest() {
        return StudyConventionUpdateRequest.builder()
                .name("컨벤션 수정")
                .description("설명 수정")
                .content("정규식 수정")
                .active(true)
                .build();
    }

테스트용 수정 컨벤션을 생성하는 메서드 Fixture이다

 

 

StudyConventionControllerTest.java

    @Test
    public void Convention_수정_테스트() throws Exception {

        //given
        User savedUser = userRepository.save(generateAuthUser());
        Map<String, String> map = TokenUtil.createTokenMap(savedUser);
        String accessToken = jwtService.generateAccessToken(map, savedUser);
        String refreshToken = jwtService.generateRefreshToken(map, savedUser);

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
        studyInfoRepository.save(studyInfo);

        StudyConvention studyConvention = StudyConventionFixture.createStudyDefaultConvention(studyInfo.getId());
        studyConventionRepository.save(studyConvention);

        StudyConventionUpdateRequest updateRequest = StudyConventionFixture.generateStudyConventionUpdateRequest();

        when(studyMemberService.isValidateStudyLeader(any(User.class), any(Long.class))).thenReturn(UserInfoResponse.of(savedUser));
        doNothing().when(studyConventionService).updateStudyConvention(any(StudyConventionUpdateRequest.class), any(Long.class));

        //when, then
        mockMvc.perform(put("/study/" + studyInfo.getId() + "/convention/" + studyConvention.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken))
                        .content(objectMapper.writeValueAsString(updateRequest)))

                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(200))
                .andExpect(jsonPath("$.res_msg").value("OK"))
                .andExpect(jsonPath("$.res_obj").value("StudyConvention update Success"))
                .andDo(print());

    }

    @Test
    public void Convention_수정_유효성_검즘_실패_테스트() throws Exception {

        //given
        String inValidContent = "   ";
        String expectedError = "content: 컨벤션 내용은 공백일 수 없습니다.";

        User savedUser = userRepository.save(generateAuthUser());
        Map<String, String> map = TokenUtil.createTokenMap(savedUser);
        String accessToken = jwtService.generateAccessToken(map, savedUser);
        String refreshToken = jwtService.generateRefreshToken(map, savedUser);

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
        studyInfoRepository.save(studyInfo);

        StudyConvention studyConvention = StudyConventionFixture.createStudyDefaultConvention(studyInfo.getId());
        studyConventionRepository.save(studyConvention);

        when(studyMemberService.isValidateStudyLeader(any(User.class), any(Long.class))).thenReturn(UserInfoResponse.of(savedUser));
        doNothing().when(studyConventionService).updateStudyConvention(any(StudyConventionUpdateRequest.class), any(Long.class));

        //when, then
        mockMvc.perform(put("/study/" + studyInfo.getId() + "/convention/" + studyConvention.getId())
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken))
                        .content(objectMapper.writeValueAsString(StudyConventionUpdateRequest.builder()
                                .name("컨벤션 수정")
                                .description("설명 수정")
                                .content(inValidContent)
                                .build())))

                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(400))
                .andExpect(jsonPath("$.res_msg").value(expectedError))
                .andDo(print());
    }

컨트롤러 동작 검증 ( HTTP 요청을 올바르게 처리하고 적절한 응답을 반환하는지 확인)

기본적인 수정 테스트와 유효성 검증이 실패하는 테스트로 나누어 테스트 진행했다.

 

 

StudyConventionServiceTest.java

    @Test
    @DisplayName("컨벤션 수정 테스트")
    public void updateStudyConvention() {
        //given
        User savedUser = userRepository.save(generateAuthUser());

        StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
        studyInfoRepository.save(studyInfo);

        StudyMember leader = StudyMemberFixture.createStudyMemberLeader(savedUser.getId(), studyInfo.getId());
        studyMemberRepository.save(leader);

        StudyConvention studyConvention = StudyConventionFixture.createStudyDefaultConvention(studyInfo.getId());
        studyConventionRepository.save(studyConvention);

        StudyConventionUpdateRequest updateRequest = StudyConventionFixture.generateStudyConventionUpdateRequest();

        //when
        studyMemberService.isValidateStudyLeader(savedUser, studyInfo.getId());
        studyConventionService.updateStudyConvention(updateRequest, studyConvention.getId());
        StudyConvention updateConvention = studyConventionRepository.findById(studyConvention.getId())
                .orElseThrow(() -> new ConventionException(ExceptionMessage.CONVENTION_NOT_FOUND));

        //then
        assertEquals("컨벤션 수정", updateConvention.getName());
        assertEquals("설명 수정", updateConvention.getDescription());
        assertEquals("정규식 수정", updateConvention.getContent());
    }

컨벤션 서비스가 잘 작동 되는지 확인하는 Service 테스트이다.

댓글