서비스 레이어(비즈니스 레이어만)

커버리지 (라인 + 브랜치) 70%

테스트 템플릿

public class FeedbackServiceTest extends IntegrationTest {

    private final String feedbackMessage = "이번 스프린트에서 아주 잘해주셨습니다.";

    @Autowired private FeedbackService feedbackService;
    @Autowired private FeedbackRepository feedbackRepository;
    @Autowired private SprintRepository sprintRepository;
    @Autowired private MemberRepository memberRepository;
    @Autowired private TeamRepository teamRepository;
    @Autowired private TeamParticipantRepository teamParticipantRepository;
    @Autowired private ProjectRepository projectRepository;
    @Autowired private ProjectParticipantRepository projectParticipantRepository;
    
    private ProjectParticipant sender;
    private ProjectParticipant anotherSender;
    private ProjectParticipant receiver;
    private ProjectParticipant anotherReceiver;
    private ProjectParticipant unknownReceiver;
    private Sprint sprint;
    private Sprint anotherSprint;
    private Project project;
    private Project anotherProject;

    @BeforeEach
    void setUp() {
        Member senderMember =
                memberRepository.save(
                        Member.createMember(
                                "testSenderNickname",
                                "testSenderProfileImageUrl",
                                OauthInfo.createOauthInfo("testOauthId", "testOauthProvider")));
        Member receiverMember =
                memberRepository.save(
                        Member.createMember(
                                "testReceiverNickname",
                                "testReceiverProfileImageUrl",
                                OauthInfo.createOauthInfo("testOauthId", "testOauthProvider")));

        // 초기에 로그인한 사용자를 sender로 설정
        setAuthenticatedUser(senderMember);

        Team team = teamRepository.save(Team.createTeam("testName", "testDescription"));

        TeamParticipant teamParticipantAdmin =
                teamParticipantRepository.save(
                        TeamParticipant.createParticipant(
                                senderMember, team, TeamParticipantRole.ADMIN));
        TeamParticipant teamParticipantUser =
                teamParticipantRepository.save(
                        TeamParticipant.createParticipant(
                                receiverMember, team, TeamParticipantRole.USER));

        project =
                projectRepository.save(
                        Project.createProject(
                                team, "testTitle", "testDescription", LocalDate.of(2030, 1, 1)));
        anotherProject =
                projectRepository.save(
                        Project.createProject(
                                team, "testTitle", "testDescription", LocalDate.of(2030, 1, 1)));

        sender =
                projectParticipantRepository.save(
                        ProjectParticipant.createProjectParticipant(
                                teamParticipantAdmin,
                                project,
                                senderMember.getNickname(),
                                senderMember.getProfileImageUrl(),
                                ProjectParticipantRole.ADMIN));

        receiver =
                projectParticipantRepository.save(
                        ProjectParticipant.createProjectParticipant(
                                teamParticipantUser,
                                project,
                                receiverMember.getNickname(),
                                receiverMember.getProfileImageUrl(),
                                ProjectParticipantRole.MEMBER));

        anotherReceiver =
                projectParticipantRepository.save(
                        ProjectParticipant.createProjectParticipant(
                                teamParticipantUser,
                                anotherProject,
                                "test",
                                "test",
                                ProjectParticipantRole.ADMIN));

        anotherSender =
                projectParticipantRepository.save(
                        ProjectParticipant.createProjectParticipant(
                                teamParticipantUser,
                                anotherProject,
                                "test",
                                "test",
                                ProjectParticipantRole.ADMIN));

        unknownReceiver =
                projectParticipantRepository.save(
                        ProjectParticipant.createProjectParticipant(
                                teamParticipantUser,
                                anotherProject,
                                "UNKNOWN_PROJECT_NICKNAME",
                                "UNKNOWN_PROJECT_PROFILE_URL",
                                ProjectParticipantRole.MEMBER));

        sprint =
                sprintRepository.save(
                        Sprint.createSprint(project, "testSprint", "testGoal", LocalDate.now()));

        anotherSprint =
                sprintRepository.save(
                        Sprint.createSprint(
                                anotherProject,
                                "testAnotherSprint",
                                "testAnotherGoal",
                                LocalDate.now()));
    }

    @Nested
    class 피드백_메시지를_전송할_때 {

        @Test
        @Transactional
        void 입력_값이_정상이라면_피드백_메시지_전송에_성공한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(sprint.getId(), receiver.getId(), feedbackMessage);

            // when
            feedbackService.sendFeedback(request);

            // then
            Feedback feedback = feedbackRepository.findById(1L).get();
            assertThat(feedback.getSender()).isEqualTo(sender);
            assertThat(feedback.getReceiver()).isEqualTo(receiver);
            assertThat(feedback.getSprint()).isEqualTo(sprint);
            assertThat(feedback.getMessage()).isEqualTo(feedbackMessage);
        }

        @Test
        void 본인에게_피드백_메시지를_전송하면_예외가_발생한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(sprint.getId(), sender.getId(), feedbackMessage);

            // when & then
            assertThatThrownBy(() -> feedbackService.sendFeedback(request))
                    .isInstanceOf(CommonException.class)
                    .hasMessage(FeedbackErrorCode.CANNOT_SEND_FEEDBACK_TO_SELF.getMessage());
        }

        @Test
        void 같은_스프린트에서_특정_대상에게_피드백_메시지를_두_번_전송하면_예외가_발생한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(sprint.getId(), receiver.getId(), feedbackMessage);
            feedbackService.sendFeedback(request);

            // when & then
            FeedbackSendRequest duplicateRequest =
                    new FeedbackSendRequest(sprint.getId(), receiver.getId(), feedbackMessage);
            assertThatThrownBy(() -> feedbackService.sendFeedback(duplicateRequest))
                    .isInstanceOf(CommonException.class)
                    .hasMessage(FeedbackErrorCode.FEEDBACK_ALREADY_SENT.getMessage());
        }

        @Test
        void 스프린트가_존재하지_않는다면_예외가_발생한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(999L, receiver.getId(), feedbackMessage);

            // when & then
            assertThatThrownBy(() -> feedbackService.sendFeedback(request))
                    .isInstanceOf(CommonException.class)
                    .hasMessage(SprintErrorCode.SPRINT_NOT_FOUND.getMessage());
        }

        @Test
        void 피드백을_받을_대상이_존재하지_않는다면_예외가_발생한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(sprint.getId(), 999L, feedbackMessage);

            // when & then
            assertThatThrownBy(() -> feedbackService.sendFeedback(request))
                    .isInstanceOf(CommonException.class)
                    .hasMessage(FeedbackErrorCode.RECEIVER_NOT_FOUND.getMessage());
        }

        @Test
        void 같은_프로젝트에_속하지_않은_대상에게_피드백_메시지를_전송하면_예외가_발생한다() {
            // given
            FeedbackSendRequest request =
                    new FeedbackSendRequest(
                            sprint.getId(), anotherReceiver.getId(), feedbackMessage);

            // when & then
            assertThatThrownBy(() -> feedbackService.sendFeedback(request))
                    .isInstanceOf(CommonException.class)
                    .hasMessage(FeedbackErrorCode.INVALID_PROJECT_PARTICIPANT.getMessage());
        }
    
    private void setAuthenticatedUser(Member member) {
    UserDetails userDetails = new PrincipalDetails(member.getId(), member.getRole());
    UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(token);
  }

서비스 테스트코드

class WineServiceImplTest extends IntegrationTestSupport {
    @Autowired
    WineRepository wineRepository;
    @Autowired
    WineService wineService;
    @Autowired
    MemberRepository memberRepository;
    @Autowired
    TastingNoteRepository tastingNoteRepository;
    @Autowired
    WineWishlistRepository wineWishlistRepository;

    @DisplayName("와인 이름을 받아서 이름을 포함하는 모든 와인을 조회한다.")
    @Test
    void searchWineByName() {
        // given
        Wine wine1 = createWine("대중적인 레드 와인 10년");
        Wine wine2 = createWine("대중적인 화이트 와인 13년");
        Wine wine3 = createWine("대중적인 화이트 스파클링 와인 20년");
        Wine wine4 = createWine("매니아들이 찾는 레드 와인 30년");
        wineRepository.saveAll(List.of(wine1, wine2, wine3, wine4));
        Pageable pageable = PageRequest.of(0, 10);

        // when
        PageResponse<WinePreviewResponse> winePreviewResponsePageResponse1 = wineService.searchWinesByName("0년", pageable);
        PageResponse<WinePreviewResponse> winePreviewResponsePageResponse2 = wineService.searchWinesByName("대중적", pageable);

        // then
        assertWinePreviewPageResponse(winePreviewResponsePageResponse1, 0, 1,
                List.of(WinePreviewResponse.of(wine1), WinePreviewResponse.of(wine3), WinePreviewResponse.of(wine4)));
        assertWinePreviewPageResponse(winePreviewResponsePageResponse2, 0, 1,
                List.of(WinePreviewResponse.of(wine1), WinePreviewResponse.of(wine3), WinePreviewResponse.of(wine2)));
    }

리포지토리 테스트코드

class WineRepositoryImplTest extends IntegrationTestSupport {
    @Autowired
    WineRepository wineRepository;
    @Autowired
    MemberRepository memberRepository;
    @Autowired
    WineWishlistRepository wineWishlistRepository;

    @DisplayName("멤버 선호 와인 종류, 지역, 가격대에 따른 와인 추천 리스트 조회")
    @Test
    void findRecommendWinesByMemberPreferSortAndAreaAndPrice() {
        // given
        Wine wine1 = createWine("와인1", "레드", "프랑스", 10000, "피노누아", 4.5f);
        Wine wine2 = createWine("와인2", "화이트", "이탈리아", 20000, "샤르도네", 4.0f);
        Wine wine3 = createWine("와인3", "로제", "스페인", 30000, "피노누아", 3.5f);
        Wine wine4 = createWine("와인4", "레드", "프랑스", 40000, "피노누아", 4.5f);
        Wine wine5 = createWine("와인5", "화이트", "이탈리아", 50000, "샤르도네", 4.0f);
        Wine wine6 = createWine("와인6", "로제", "스페인", 60000, "피노누아", 3.5f);
        Wine wine7 = createWine("와인7", "레드", "미국", 15000, "카베르네 소비뇽", 4.2f);
        Wine wine8 = createWine("와인8", "화이트", "독일", 25000, "리슬링", 4.3f);
        Wine wine9 = createWine("와인9", "스파클링", "프랑스", 35000, "샴페인", 4.6f);
        Wine wine10 = createWine("와인10", "디저트", "포르투갈", 45000, "포트 와인", 4.7f);
        Wine wine11 = createWine("와인11", "레드", "스페인", 55000, "템프라니요", 4.1f);
        Wine wine12 = createWine("와인12", "화이트", "뉴질랜드", 65000, "소비뇽 블랑", 4.4f);
        wineRepository.saveAll(List.of(wine1, wine2, wine3, wine4, wine5, wine6, wine7, wine8, wine9, wine10, wine11, wine12));

        // when
         List<Wine> recommendWines = wineRepository.findRecommendWinesBy(List.of("프랑스", "이탈리아"), List.of("레드", "화이트"), 60000L);

         // then
         assertThat(recommendWines)
                 .hasSize(2)
                 .extracting("name")
                 .containsExactlyInAnyOrder("와인1", "와인4");
    }