Skip to content

Commit bf0b7ef

Browse files
authored
Merge pull request #217 from Runnect/tests/scrap-service-unit-tests
test: ScrapService ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€ + ์Šคํฌ๋žฉ ์ทจ์†Œ NPE ์ˆ˜์ •
2 parents a706593 + 6cad2f2 commit bf0b7ef

2 files changed

Lines changed: 260 additions & 1 deletion

File tree

โ€Žsrc/main/java/org/runnect/server/scrap/service/ScrapService.javaโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public CreateAndDeleteScrapResponseDto createAndDeleteScrap(Long userId, CreateA
5454
}
5555
}
5656
// ์Šคํฌ๋žฉ ์‚ญ์ œ
57-
else {
57+
else if (scrap != null) {
58+
// ์Šคํฌ๋žฉํ•œ ์ ์ด ์—†๋Š” ์ฝ”์Šค๋ฅผ ์ทจ์†Œ ์š”์ฒญํ•˜๋ฉด ํ•  ๊ฒŒ ์—†์œผ๋‹ˆ ๊ทธ๋ƒฅ ๋ฌด์‹œํ•œ๋‹ค (NPE ๋ฐฉ์ง€)
5859
scrap.updateScrapTF(false);
5960
}
6061

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
package org.runnect.server.scrap.service;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatCode;
5+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6+
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.Mockito.never;
8+
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.when;
10+
11+
import java.util.Arrays;
12+
import java.util.Collections;
13+
import java.util.Optional;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.DisplayName;
16+
import org.junit.jupiter.api.Nested;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.extension.ExtendWith;
19+
import org.locationtech.jts.geom.LineString;
20+
import org.mockito.Mock;
21+
import org.mockito.junit.jupiter.MockitoExtension;
22+
import org.runnect.server.common.exception.NotFoundException;
23+
import org.runnect.server.common.module.convert.CoordinateDto;
24+
import org.runnect.server.common.module.convert.CoordinatePathConverter;
25+
import org.runnect.server.course.entity.Course;
26+
import org.runnect.server.publicCourse.entity.PublicCourse;
27+
import org.runnect.server.publicCourse.repository.PublicCourseRepository;
28+
import org.runnect.server.scrap.dto.request.CreateAndDeleteScrapRequestDto;
29+
import org.runnect.server.scrap.dto.response.CreateAndDeleteScrapResponseDto;
30+
import org.runnect.server.scrap.dto.response.GetScrapCourseResponseDto;
31+
import org.runnect.server.scrap.entity.Scrap;
32+
import org.runnect.server.scrap.repository.ScrapRepository;
33+
import org.runnect.server.user.entity.RunnectUser;
34+
import org.runnect.server.user.entity.SocialType;
35+
import org.runnect.server.user.entity.StampType;
36+
import org.runnect.server.user.exception.userException.NotFoundUserException;
37+
import org.runnect.server.user.repository.UserRepository;
38+
import org.runnect.server.user.service.UserStampService;
39+
import org.springframework.beans.BeanUtils;
40+
import org.springframework.test.util.ReflectionTestUtils;
41+
42+
@ExtendWith(MockitoExtension.class)
43+
class ScrapServiceTest {
44+
45+
@Mock
46+
private ScrapRepository scrapRepository;
47+
@Mock
48+
private UserRepository userRepository;
49+
@Mock
50+
private PublicCourseRepository publicCourseRepository;
51+
@Mock
52+
private UserStampService userStampService;
53+
54+
private ScrapService scrapService;
55+
56+
@BeforeEach
57+
void setUp() {
58+
scrapService = new ScrapService(scrapRepository, userRepository, publicCourseRepository,
59+
userStampService);
60+
}
61+
62+
private RunnectUser buildUser(Long id) {
63+
RunnectUser user = RunnectUser.builder()
64+
.nickname("๋Ÿฌ๋„ˆ" + id)
65+
.socialId("social-" + id)
66+
.email("user" + id + "@runnect.io")
67+
.provider(SocialType.KAKAO)
68+
.build();
69+
ReflectionTestUtils.setField(user, "id", id);
70+
return user;
71+
}
72+
73+
private LineString validLineString() {
74+
return CoordinatePathConverter.coorConvertPath(Arrays.asList(
75+
new CoordinateDto(37.5665, 126.9780),
76+
new CoordinateDto(37.5651, 126.9895)
77+
));
78+
}
79+
80+
private PublicCourse buildPublicCourse(Long id, RunnectUser owner) {
81+
Course course = Course.builder()
82+
.runnectUser(owner)
83+
.title("์ฝ”์Šค ์ œ๋ชฉ")
84+
.departureRegion("๊ฒฝ๊ธฐ")
85+
.departureCity("์‹œํฅ์‹œ")
86+
.departureTown("์ •์™•๋™")
87+
.departureDetail("์ •์™•๋ณธ๋™")
88+
.departureName("์ •์™•์—ญ")
89+
.distance(5.2f)
90+
.image("https://image.example/course.png")
91+
.path(validLineString())
92+
.build();
93+
ReflectionTestUtils.setField(course, "id", 10L);
94+
ReflectionTestUtils.setField(course, "isPrivate", false);
95+
96+
PublicCourse publicCourse = PublicCourse.builder()
97+
.course(course)
98+
.title("๊ณต๊ฐœ ์ฝ”์Šค")
99+
.description("์„ค๋ช…")
100+
.build();
101+
ReflectionTestUtils.setField(publicCourse, "id", id);
102+
return publicCourse;
103+
}
104+
105+
private Scrap buildScrap(Long id, RunnectUser user, PublicCourse publicCourse, boolean scrapTF) {
106+
Scrap scrap = Scrap.builder().runnectUser(user).publicCourse(publicCourse).scrapTF(scrapTF).build();
107+
ReflectionTestUtils.setField(scrap, "id", id);
108+
return scrap;
109+
}
110+
111+
private CreateAndDeleteScrapRequestDto requestDto(Long publicCourseId, boolean scrapTF) {
112+
CreateAndDeleteScrapRequestDto dto = BeanUtils.instantiateClass(CreateAndDeleteScrapRequestDto.class);
113+
ReflectionTestUtils.setField(dto, "publicCourseId", publicCourseId);
114+
ReflectionTestUtils.setField(dto, "scrapTF", scrapTF);
115+
return dto;
116+
}
117+
118+
@Nested
119+
@DisplayName("createAndDeleteScrap")
120+
class CreateAndDeleteScrap {
121+
122+
@Test
123+
@DisplayName("์Šคํฌ๋žฉํ•œ ์  ์—†๋Š” ์ฝ”์Šค๋ฅผ ์ƒˆ๋กœ ์Šคํฌ๋žฉํ•˜๋ฉด ์‹ ๊ทœ ์ƒ์„ฑ๋˜๊ณ  ์œ ์ € ์ •๋ณด๊ฐ€ ๊ฐฑ์‹ ๋œ๋‹ค")
124+
void ์‹ ๊ทœ_์Šคํฌ๋žฉ() {
125+
RunnectUser user = buildUser(1L);
126+
PublicCourse publicCourse = buildPublicCourse(100L, user);
127+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.empty());
128+
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
129+
when(publicCourseRepository.findById(100L)).thenReturn(Optional.of(publicCourse));
130+
when(scrapRepository.countByPublicCourseAndScrapTFIsTrue(publicCourse)).thenReturn(1L);
131+
132+
CreateAndDeleteScrapResponseDto response = scrapService.createAndDeleteScrap(1L,
133+
requestDto(100L, true));
134+
135+
assertThat(response.getScrapCount()).isEqualTo(1L);
136+
assertThat(response.getScrapTF()).isTrue();
137+
assertThat(user.getCreatedScrap()).isEqualTo(1L);
138+
verify(userStampService).createStampByUser(user, StampType.s);
139+
verify(scrapRepository).save(any(Scrap.class));
140+
}
141+
142+
@Test
143+
@DisplayName("์ด๋ฏธ ์ทจ์†Œ๋œ ์Šคํฌ๋žฉ์„ ๋‹ค์‹œ ์Šคํฌ๋žฉํ•˜๋ฉด ๊ธฐ์กด ํ–‰์„ ์žฌํ™œ์„ฑํ™”ํ•˜๊ณ  ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค")
144+
void ๊ธฐ์กด_์Šคํฌ๋žฉ_์žฌํ™œ์„ฑํ™”() {
145+
RunnectUser user = buildUser(1L);
146+
PublicCourse publicCourse = buildPublicCourse(100L, user);
147+
Scrap existingScrap = buildScrap(50L, user, publicCourse, false);
148+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.of(existingScrap));
149+
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
150+
when(publicCourseRepository.findById(100L)).thenReturn(Optional.of(publicCourse));
151+
when(scrapRepository.countByPublicCourseAndScrapTFIsTrue(publicCourse)).thenReturn(1L);
152+
153+
scrapService.createAndDeleteScrap(1L, requestDto(100L, true));
154+
155+
assertThat(existingScrap.getScrapTF()).isTrue();
156+
assertThat(user.getCreatedScrap()).isEqualTo(0L);
157+
verify(scrapRepository, never()).save(any());
158+
verify(userStampService, never()).createStampByUser(any(), any());
159+
}
160+
161+
@Test
162+
@DisplayName("์Šคํฌ๋žฉํ•œ ์ฝ”์Šค๋ฅผ ์ทจ์†Œํ•˜๋ฉด scrapTF๊ฐ€ false๊ฐ€ ๋œ๋‹ค")
163+
void ์Šคํฌ๋žฉ_์ทจ์†Œ() {
164+
RunnectUser user = buildUser(1L);
165+
PublicCourse publicCourse = buildPublicCourse(100L, user);
166+
Scrap existingScrap = buildScrap(50L, user, publicCourse, true);
167+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.of(existingScrap));
168+
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
169+
when(publicCourseRepository.findById(100L)).thenReturn(Optional.of(publicCourse));
170+
when(scrapRepository.countByPublicCourseAndScrapTFIsTrue(publicCourse)).thenReturn(0L);
171+
172+
scrapService.createAndDeleteScrap(1L, requestDto(100L, false));
173+
174+
assertThat(existingScrap.getScrapTF()).isFalse();
175+
}
176+
177+
@Test
178+
@DisplayName("์Šคํฌ๋žฉํ•œ ์  ์—†๋Š” ์ฝ”์Šค๋ฅผ ์ทจ์†Œ ์š”์ฒญํ•ด๋„ ์˜ˆ์™ธ ์—†์ด ์ฒ˜๋ฆฌ๋œ๋‹ค")
179+
void ์Šคํฌ๋žฉํ•œ_์ _์—†๋Š”_์ฝ”์Šค_์ทจ์†Œ_์š”์ฒญ์€_๋ฌด์‹œ๋œ๋‹ค() {
180+
RunnectUser user = buildUser(1L);
181+
PublicCourse publicCourse = buildPublicCourse(100L, user);
182+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.empty());
183+
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
184+
when(publicCourseRepository.findById(100L)).thenReturn(Optional.of(publicCourse));
185+
when(scrapRepository.countByPublicCourseAndScrapTFIsTrue(publicCourse)).thenReturn(0L);
186+
187+
assertThatCode(() -> scrapService.createAndDeleteScrap(1L, requestDto(100L, false)))
188+
.doesNotThrowAnyException();
189+
190+
verify(scrapRepository, never()).save(any());
191+
}
192+
193+
@Test
194+
@DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์œ ์ €๋ฉด NotFoundUserException")
195+
void ์กด์žฌํ•˜์ง€_์•Š๋Š”_์œ ์ €() {
196+
RunnectUser dummyOwner = buildUser(2L);
197+
PublicCourse publicCourse = buildPublicCourse(100L, dummyOwner);
198+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.empty());
199+
when(userRepository.findById(1L)).thenReturn(Optional.empty());
200+
201+
assertThatThrownBy(() -> scrapService.createAndDeleteScrap(1L, requestDto(100L, true)))
202+
.isInstanceOf(NotFoundUserException.class);
203+
}
204+
205+
@Test
206+
@DisplayName("์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ณต๊ฐœ ์ฝ”์Šค๋ฉด NotFoundException")
207+
void ์กด์žฌํ•˜์ง€_์•Š๋Š”_๊ณต๊ฐœ์ฝ”์Šค() {
208+
RunnectUser user = buildUser(1L);
209+
when(scrapRepository.findByUserIdAndPublicCourseId(1L, 100L)).thenReturn(Optional.empty());
210+
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
211+
when(publicCourseRepository.findById(100L)).thenReturn(Optional.empty());
212+
213+
assertThatThrownBy(() -> scrapService.createAndDeleteScrap(1L, requestDto(100L, true)))
214+
.isInstanceOf(NotFoundException.class);
215+
}
216+
}
217+
218+
@Nested
219+
@DisplayName("getScrapCourseByUser")
220+
class GetScrapCourseByUser {
221+
222+
@Test
223+
@DisplayName("์Šคํฌ๋žฉํ•œ ์ฝ”์Šค ๋ชฉ๋ก์„ ๋งคํ•‘ํ•ด์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
224+
void ์ •์ƒ_์กฐํšŒ() {
225+
RunnectUser user = buildUser(1L);
226+
PublicCourse publicCourse = buildPublicCourse(100L, user);
227+
Scrap scrap = buildScrap(50L, user, publicCourse, true);
228+
when(scrapRepository.findAllByUserIdAndScrapTF(1L)).thenReturn(
229+
Optional.of(Collections.singletonList(scrap)));
230+
231+
GetScrapCourseResponseDto response = scrapService.getScrapCourseByUser(1L);
232+
233+
assertThat(response.getUser().getUserId()).isEqualTo(1L);
234+
assertThat(response.getScraps()).hasSize(1);
235+
assertThat(response.getScraps().get(0).getPublicCourseId()).isEqualTo(100L);
236+
assertThat(response.getScraps().get(0).getDeparture().getRegion()).isEqualTo("๊ฒฝ๊ธฐ");
237+
}
238+
239+
@Test
240+
@DisplayName("์Šคํฌ๋žฉํ•œ ์ฝ”์Šค๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค")
241+
void ์Šคํฌ๋žฉ_์—†์Œ() {
242+
when(scrapRepository.findAllByUserIdAndScrapTF(1L)).thenReturn(Optional.of(Collections.emptyList()));
243+
244+
GetScrapCourseResponseDto response = scrapService.getScrapCourseByUser(1L);
245+
246+
assertThat(response.getScraps()).isEmpty();
247+
}
248+
249+
@Test
250+
@DisplayName("๋ ˆํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ๋นˆ Optional์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด NotFoundException")
251+
void ์กฐํšŒ_๊ฒฐ๊ณผ๊ฐ€_์—†์œผ๋ฉด_์˜ˆ์™ธ() {
252+
when(scrapRepository.findAllByUserIdAndScrapTF(1L)).thenReturn(Optional.empty());
253+
254+
assertThatThrownBy(() -> scrapService.getScrapCourseByUser(1L))
255+
.isInstanceOf(NotFoundException.class);
256+
}
257+
}
258+
}

0 commit comments

Comments
ย (0)