101DAYS) [Main-Project] 테스트코드 Spring Security 관련 에러 해결, 멘토링

nacSeo (낙서)·2023년 3월 14일
0

@Profile 사용해서 yaml파일 이용해 spring security off 설정
다들 각자 개인적인 작업을 하고 있어서 그대로 진행하기로 하고 오전 회의는 스킵했다. Review와 ReviewLike에 대한 Test 코드를 작성하여 Restdocs 명세를 뽑아내는 작업을 진행 중이었다. 코스 과정 때 배우고, pre-project에서 했던대로 테스트 코드를 짰으나, 에러가 발생했다.

reviewController의 Post 코드

@RestController
@RequestMapping("/reviews")
@RequiredArgsConstructor
public class ReviewController {
    private final ReviewService reviewService;
    private final ReviewLikeService reviewLikeService;
    private final MemberService memberService;
    private final ReviewMapper mapper;
    @PostMapping
    public ResponseEntity postReview(@RequestBody ReviewDto.Post postDto, Principal principal) {
        Member member = memberService.findLoginMemberByEmail(principal.getName());

        postDto.setMember(member);

        Review review = mapper.reviewPostDtoToReview(postDto);

        Review response = reviewService.createReview(review);

        return new ResponseEntity<>(mapper.reviewToReviewResponseDto(response), HttpStatus.CREATED);
    }
}

위의 코드를 바탕으로 한 테스트 코드

@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureRestDocs
public class ReviewControllerRestDocsTest {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private Gson gson;
    @MockBean
    private ReviewService reviewService;
    @MockBean
    private ReviewLikeService reviewLikeService;
    @MockBean
    private ReviewMapper mapper;

    @Test
    void postReviewTest() throws Exception {
        // given
        ReviewDto.Post post = new ReviewDto.Post("이미지", "텍스트", new Member());
        String content = gson.toJson(post);

        given(mapper.reviewPostDtoToReview(Mockito.any(ReviewDto.Post.class))).willReturn(new Review());

        Review mockResultReview = new Review();
        mockResultReview.setId(1L);
        given(reviewService.createReview(Mockito.any(Review.class))).willReturn(new Review());

        // when
        ResultActions actions =
                mockMvc.perform(
                        post("/reviews")
                                .accept(MediaType.APPLICATION_JSON)
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(content)
                );

        // then
        actions
                .andExpect(status().isCreated())
                .andExpect(header().string("Location", is(startsWith("/reviews/"))))
                .andDo(document(
                        "post-review",
                        getRequestPreProcessor(),
                        getResponsePreProcessor(),
                        requestFields(
                                List.of(
                                        fieldWithPath("reviewImage").type(JsonFieldType.STRING).description("리뷰 이미지"),
                                        fieldWithPath("reviewText").type(JsonFieldType.STRING).description("리뷰 텍스트")
                                )
                        ),
                        responseHeaders(
                                headerWithName(HttpHeaders.LOCATION).description("Location header. 등록된 리소스의 URI")
                        ),
                        responseFields(
                                List.of(
                                        fieldWithPath("id").type(JsonFieldType.NUMBER).description("리뷰 식별자"),
                                        fieldWithPath("reviewImage").type(JsonFieldType.STRING).description("리뷰 이미지"),
                                        fieldWithPath("reviewText").type(JsonFieldType.STRING).description("리뷰 텍스트"),
                                        fieldWithPath("reviewLikeCount").type(JsonFieldType.NUMBER).description("리뷰 좋아요 개수"),
                                        fieldWithPath("createdAt").type(JsonFieldType.STRING).description("리뷰 생성 시간"),
                                        fieldWithPath("modifiedAt").type(JsonFieldType.STRING).description("리뷰 수정 시간")
                                )
                        )
                ));
    }
}

그 결과, .andExpect(status().isCreated())에서 400 에러가 발생했고, Principal 인터페이스를 이용하여 문제가 있는 것 같다고 판단하며 에러핸들링을 진행하였다.
400 에러는 클라이언트 측에서 잘못된 요청을 보냈을 때 발생하는 에러다. RequestBody에 Member 객체를 생성하여 넣어주었지만, 실제로는 Principal 객체를 이용하여 로그인한 회원의 정보를 받아와 Member 객체를 생성하고 있다. 따라서, 테스트 코드에서도 Principal 객체를 이용하여 로그인한 회원의 정보를 받아온 후에 RequestBody에 넣어주어야 하므로 ResponseDto를 활용하여 ResponseBody에 로그인한 회원 정보를 넣어주면서 에러를 해결하였다.

이 후, 저녁에는 BE 2주차 멘토링을 진행하였다. 멘토링을 준비하면서 2주차 개인 작업현황을 정리해봤다.

멘토링을 진행하면서 내용을 정리했다.






멘토링이 끝난 후, 늦은 시간이었지만 피드백을 통해 팀원들과 회의를 통해 몇 가지 내용들을 결정했다.

정리해보자면,
1. 결합도와 응집도가 중요하단 말을 말로만 들어왔는데 정작 프로젝트에 적용을 못시키고 Controller, Service 등등 각자의 역할이 중구난방인 현재 코드를 Composite 패턴을 사용하여 리팩토링하기로 했다.
2. API 문서화에 있어 Swagger 등 다양한 방식이 존재하지만, 짧은 프로젝트 기간동안 배워본 적 없는(큰 러닝커브를 가진) 다른 방법으로 진행하는 것보다 테스트 코드를 짜는 데에 힘들더라도 Restdocs 방식을 유지하기로 했다.
3. yaml(yml)파일을 application.yml, application-local.yml, application-deploy.yml 등 분리하여 사용함으로써 테스트 코드에서 Spring Security를 제외시켜 Mockito를 사용하여 보다 쉽게 테스트 코드를 짤 수 있게 하기로 했다.
4. 무한 스크롤을 사용하는 데에 있어 cache 방법을 사용하는 게 제일 맞는 방법이라 생각했다.
5. 현재 main 브랜치, dev 브랜치, dev-be 브랜치, dev-fe 브랜치, feat/[작업할 기능명]을 통해 github를 사용하고 있었다. 현업에서는 issue 단위별로 브랜치를 생성하여 사용하고, 완료 시 브랜치를 삭제하는 방식으로 진행한다고 피드백을 받았다. 물론 피드백대로 현업에서 사용하듯이 하는 게 옳은 방법이라고 생각했으나, 아직 git에 익숙치 않고 브랜치를 많이 신경쓰며 작업하기에 팀원들이 힘들 것 같다는 의견이 있었다. 따라서, 최대한 작은 단위로 커밋 및 pr을 진행하며 브랜치 운영을 큰 변화없이 가져가기로 했다. 추가적으로 현재 pr을 하면 작업 속도를 위해 스스로가 바로바로 merge를 하고 있었는데, 팀원 최소 1명에게 pr 리뷰를 받고 approve하기로 정했다.
6. 앞으로 점점 작업할 내용들이 겹치게 될텐데, 그럴 경우 conflict 문제로 인해 따로 작업하기가 수월치 않다고 생각했다. 멘토분께서 현업에서도 그럴 경우가 종종 발생하고, 발생한다면 페어프로그래밍 등으로 작업을 해내간다고 피드백을 해주면서 그 방법을 사용해보기로 했다.
7. 배포를 위한 AWS 설정에서 인바운드 설정이 존재하는데, 현재 프로젝트는 상업용도 아니고 크게 중요한 프로젝트가 아니라고 생각이 들어 따로 인바운드를 닫아두지 않도록 하기로 했다.

팀원 회의까지 끝나고나니 어느 덧 새벽이 되었다 😅 그러나 리팩토링도 해야하고 할 게 더 많아진 느낌 ,, 계속 열심히 달려봐야겠다 🔥

profile
백엔드 개발자 김창하입니다 🙇‍♂️

0개의 댓글