1+ package BE_Elixir .Elixir .domain .challenge .service ;
2+
3+ import BE_Elixir .Elixir .domain .challenge .dto .response .ChallengeCompletedResponseDTO ;
4+ import BE_Elixir .Elixir .domain .challenge .dto .response .ChallengeProgressResponseDTO ;
5+ import BE_Elixir .Elixir .domain .challenge .entity .Challenge ;
6+ import BE_Elixir .Elixir .domain .challenge .entity .ChallengeAchievement ;
7+ import BE_Elixir .Elixir .domain .challenge .entity .ChallengeAchievementId ;
8+ import BE_Elixir .Elixir .domain .challenge .repository .ChallengeAchievementRepository ;
9+ import BE_Elixir .Elixir .domain .challenge .repository .ChallengeRepository ;
10+ import BE_Elixir .Elixir .domain .member .entity .Member ;
11+ import BE_Elixir .Elixir .domain .member .repository .MemberRepository ;
12+ import BE_Elixir .Elixir .global .exception .CustomException ;
13+ import BE_Elixir .Elixir .global .exception .ErrorCode ;
14+ import org .junit .jupiter .api .*;
15+ import org .junit .jupiter .api .extension .ExtendWith ;
16+ import org .mockito .InjectMocks ;
17+ import org .mockito .Mock ;
18+ import org .mockito .junit .jupiter .MockitoExtension ;
19+
20+ import java .time .LocalDate ;
21+ import java .time .LocalDateTime ;
22+ import java .util .Optional ;
23+
24+ import static org .assertj .core .api .Assertions .assertThat ;
25+ import static org .junit .jupiter .api .Assertions .assertThrows ;
26+ import static org .mockito .ArgumentMatchers .*;
27+ import static org .mockito .Mockito .*;
28+
29+
30+ @ ExtendWith (MockitoExtension .class )
31+ @ DisplayName ("ChallengeAchievementService 테스트" )
32+ class ChallengeAchievementServiceTest {
33+ @ InjectMocks
34+ private ChallengeAchievementService service ;
35+
36+ @ Mock private ChallengeRepository challengeRepository ;
37+ @ Mock private ChallengeAchievementRepository challengeAchievementRepository ;
38+ @ Mock private MemberRepository memberRepository ;
39+
40+ private Challenge challenge ;
41+ private ChallengeAchievement achievement ;
42+ private Member member ;
43+ private Long challengeId ;
44+
45+ @ BeforeEach
46+ void setUp () {
47+ member = Member .builder ()
48+ .id (1L )
49+ .email ("test@example.com" )
50+ .nickname ("testUser" )
51+ .build ();
52+
53+ challengeId = 100L ;
54+
55+ challenge = new Challenge ();
56+ challenge .setId (challengeId );
57+ challenge .setName ("건강왕" );
58+ challenge .setYear (2025 );
59+ challenge .setMonth (8 );
60+ challenge .setAchievementName ("건강왕" );
61+ challenge .setAchievementImageUrl ("achievement.png" );
62+
63+ achievement = new ChallengeAchievement ();
64+ achievement .setMemberId (member .getId ());
65+ achievement .setChallengeId (challengeId );
66+
67+ // 모든 목표 달성
68+ achievement .setStep1Goal1Achieved (true );
69+ achievement .setStep1Goal2Achieved (true );
70+ achievement .setStep2Goal1Achieved (true );
71+ achievement .setStep2Goal2Achieved (true );
72+ achievement .setStep3Goal1Achieved (true );
73+ achievement .setStep3Goal2Achieved (true );
74+ achievement .setStep4Goal1Achieved (true );
75+ achievement .setStep4Goal2Achieved (true );
76+ }
77+
78+ @ Nested
79+ @ DisplayName ("save 메서드" )
80+ class SaveTests {
81+ @ Test
82+ @ DisplayName ("성공: 챌린지 달성 정보 저장" )
83+ void save_success () {
84+ // given
85+ when (challengeAchievementRepository .save (any ())).thenReturn (achievement );
86+
87+ // when
88+ service .save (achievement );
89+
90+ // then
91+ verify (challengeAchievementRepository , times (1 )).save (achievement );
92+ }
93+ }
94+
95+ @ Nested
96+ @ DisplayName ("getProgress 메서드" )
97+ class GetProgressTests {
98+ @ Test
99+ @ DisplayName ("성공: 진행 상황 조회" )
100+ void getProgress_success () {
101+ // given
102+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ())).thenReturn (Optional .of (challenge ));
103+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
104+ .thenReturn (Optional .of (achievement ));
105+ when (challengeRepository .findById (challengeId )).thenReturn (Optional .of (challenge ));
106+
107+ // when
108+ ChallengeProgressResponseDTO result = service .getProgress (member .getId ());
109+
110+ // then
111+ assertThat (result ).isNotNull ();
112+ assertThat (result .getName ()).isEqualTo ("건강왕" );
113+ }
114+
115+ @ Test
116+ @ DisplayName ("예외: 해당 월 챌린지가 없으면 CustomException 발생" )
117+ void getProgress_fail_noChallenge () {
118+ // given
119+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ()))
120+ .thenReturn (Optional .empty ());
121+
122+ // when & then
123+ assertThrows (CustomException .class , () -> service .getProgress (member .getId ()));
124+ }
125+ }
126+
127+ @ Nested
128+ @ DisplayName ("createIfNotExists 메서드" )
129+ class CreateIfNotExistsTests {
130+ @ Test
131+ @ DisplayName ("성공: 기존 데이터 반환" )
132+ void createIfNotExists_existing () {
133+ // given
134+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ()))
135+ .thenReturn (Optional .of (challenge ));
136+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
137+ .thenReturn (Optional .of (achievement ));
138+
139+ // when
140+ ChallengeAchievement result = service .createIfNotExists (member .getId ());
141+
142+ // then
143+ assertThat (result ).isSameAs (achievement );
144+ }
145+
146+ @ Test
147+ @ DisplayName ("성공: 기존 데이터 없으면 새로 생성" )
148+ void createIfNotExists_new () {
149+ // given
150+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ()))
151+ .thenReturn (Optional .of (challenge ));
152+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
153+ .thenReturn (Optional .empty ());
154+ when (challengeAchievementRepository .save (any ())).thenReturn (achievement );
155+
156+ // when
157+ ChallengeAchievement result = service .createIfNotExists (member .getId ());
158+
159+ // then
160+ assertThat (result ).isSameAs (achievement );
161+ verify (challengeAchievementRepository ).save (any ());
162+ }
163+
164+ @ Test
165+ @ DisplayName ("예외: 해당 월 챌린지가 없으면 CustomException 발생" )
166+ void createIfNotExists_fail_noChallenge () {
167+ // given
168+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ()))
169+ .thenReturn (Optional .empty ());
170+
171+ // when & then
172+ assertThrows (CustomException .class , () -> service .createIfNotExists (member .getId ()));
173+ }
174+ }
175+
176+ @ Nested
177+ @ DisplayName ("getChallengeCompletion 메서드" )
178+ class GetChallengeCompletionTests {
179+ @ Test
180+ @ DisplayName ("성공: 모든 목표 달성 후 완료 상태 반환" )
181+ void getChallengeCompletion_completed () {
182+ // given
183+ achievement .setStep4Goal1Achieved (true );
184+ achievement .setStep4Goal2Achieved (true );
185+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ())).thenReturn (Optional .of (challenge ));
186+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
187+ .thenReturn (Optional .of (achievement ));
188+ when (challengeRepository .findById (challengeId )).thenReturn (Optional .of (challenge ));
189+ when (challengeAchievementRepository .save (any ())).thenReturn (achievement );
190+
191+ // when
192+ ChallengeCompletedResponseDTO result = service .getChallengeCompletion (member .getId ());
193+
194+ // then
195+ assertThat (result .isChallengeCompleted ()).isTrue ();
196+ }
197+
198+ @ Test
199+ @ DisplayName ("예외: 해당 월 챌린지가 없으면 CustomException 발생" )
200+ void getChallengeCompletion_fail_noChallenge () {
201+ // given
202+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ()))
203+ .thenReturn (Optional .empty ());
204+
205+ // when & then
206+ assertThrows (CustomException .class , () -> service .getChallengeCompletion (member .getId ()));
207+ }
208+ }
209+
210+ @ Nested
211+ @ DisplayName ("getBeforeProgress 메서드" )
212+ class GetBeforeProgressTests {
213+ @ Test
214+ @ DisplayName ("성공: 이전 진행 상황 조회 (기록 있음)" )
215+ void getBeforeProgress_withRecord () {
216+ // given
217+ when (challengeRepository .findById (challengeId )).thenReturn (Optional .of (challenge ));
218+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
219+ .thenReturn (Optional .of (achievement ));
220+
221+ // when
222+ ChallengeProgressResponseDTO result = service .getBeforeProgress (member .getId (), challengeId );
223+
224+ // then
225+ assertThat (result ).isNotNull ();
226+ }
227+
228+ @ Test
229+ @ DisplayName ("성공: 이전 진행 상황 조회 (기록 없음)" )
230+ void getBeforeProgress_noRecord () {
231+ // given
232+ when (challengeRepository .findById (challengeId )).thenReturn (Optional .of (challenge ));
233+ when (challengeAchievementRepository .findByChallengeIdAndMemberId (challengeId , member .getId ()))
234+ .thenReturn (Optional .empty ());
235+
236+ // when
237+ ChallengeProgressResponseDTO result = service .getBeforeProgress (member .getId (), challengeId );
238+
239+ // then
240+ assertThat (result ).isNotNull ();
241+ }
242+
243+ @ Test
244+ @ DisplayName ("예외: 해당 챌린지가 없으면 CustomException 발생" )
245+ void getBeforeProgress_fail_noChallenge () {
246+ // given
247+ when (challengeRepository .findById (challengeId )).thenReturn (Optional .empty ());
248+
249+ // when & then
250+ assertThrows (CustomException .class , () -> service .getBeforeProgress (member .getId (), challengeId ));
251+ }
252+ }
253+
254+ @ Nested
255+ @ DisplayName ("challengeParticipation 메서드" )
256+ class ChallengeParticipationTests {
257+ @ Test
258+ @ DisplayName ("성공: 기존 기록이 없으면 새로 저장" )
259+ void challengeParticipation_new () {
260+ // given
261+ when (memberRepository .findByEmail (anyString ()))
262+ .thenReturn (Optional .of (member ));
263+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ())).thenReturn (Optional .of (challenge ));
264+ when (challengeAchievementRepository .existsById (any (ChallengeAchievementId .class ))).thenReturn (false );
265+
266+ // when
267+ service .challengeParticipation ("test@test.com" );
268+
269+ // then
270+ verify (challengeAchievementRepository ).save (any (ChallengeAchievement .class ));
271+ }
272+
273+ @ Test
274+ @ DisplayName ("성공: 이번 달 챌린지가 없으면 저장하지 않음" )
275+ void challengeParticipation_noChallengeThisMonth () {
276+ // given
277+ when (memberRepository .findByEmail (anyString ()))
278+ .thenReturn (Optional .of (member ));
279+ when (challengeRepository .findByYearAndMonth (anyInt (), anyInt ())).thenReturn (Optional .empty ());
280+
281+ // when
282+ service .challengeParticipation ("test@test.com" );
283+
284+ // then
285+ verify (challengeAchievementRepository , never ()).save (any ());
286+ }
287+
288+ @ Test
289+ @ DisplayName ("예외: 회원이 없으면 CustomException 발생" )
290+ void challengeParticipation_fail_noMember () {
291+ // given
292+ when (memberRepository .findByEmail (anyString ())).thenReturn (Optional .empty ());
293+
294+ // when & then
295+ assertThrows (CustomException .class , () -> service .challengeParticipation ("test@test.com" ));
296+ }
297+ }
298+ }
0 commit comments