Skip to content

Commit f923196

Browse files
committed
test: geohash 방식 기반으로 단위 테스트 코드 수정
1 parent 8b9c31c commit f923196

File tree

3 files changed

+283
-53
lines changed

3 files changed

+283
-53
lines changed

src/test/java/com/example/log4u/domain/Map/service/MapServiceTest.java

Lines changed: 258 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import static org.assertj.core.api.AssertionsForInterfaceTypes.*;
44
import static org.mockito.BDDMockito.*;
55

6+
import java.util.Collections;
67
import java.util.List;
8+
import java.util.Optional;
9+
import java.util.Set;
710

811
import org.junit.jupiter.api.DisplayName;
912
import org.junit.jupiter.api.Test;
@@ -12,13 +15,20 @@
1215
import org.mockito.Mock;
1316
import org.mockito.junit.jupiter.MockitoExtension;
1417

18+
import com.example.log4u.domain.diary.entity.Diary;
1519
import com.example.log4u.domain.diary.repository.DiaryRepository;
16-
import com.example.log4u.domain.map.dto.response.DiaryMarkerResponseDto;
17-
import com.example.log4u.fixture.AreaClusterFixture;
20+
import com.example.log4u.domain.diary.service.DiaryGeohashService;
21+
import com.example.log4u.domain.diary.service.DiaryService;
22+
import com.example.log4u.domain.map.cache.dao.ClusterCacheDao;
23+
import com.example.log4u.domain.map.cache.dao.DiaryCacheDao;
1824
import com.example.log4u.domain.map.dto.response.DiaryClusterResponseDto;
25+
import com.example.log4u.domain.map.dto.response.DiaryMarkerResponseDto;
26+
import com.example.log4u.domain.map.exception.InvalidGeohashException;
27+
import com.example.log4u.domain.map.exception.InvalidMapLevelException;
1928
import com.example.log4u.domain.map.repository.sido.SidoAreasRepository;
2029
import com.example.log4u.domain.map.repository.sigg.SiggAreasRepository;
2130
import com.example.log4u.domain.map.service.MapService;
31+
import com.example.log4u.fixture.DiaryFixture;
2232
import com.example.log4u.fixture.DiaryMarkerFixture;
2333

2434
@DisplayName("지도 API 단위 테스트")
@@ -37,64 +47,259 @@ class MapServiceTest {
3747
@Mock
3848
private DiaryRepository diaryRepository;
3949

40-
// @DisplayName("성공 테스트: 줌레벨이 10 이하이면 시/도 클러스터 조회")
41-
// @Test
42-
// void getDiaryClusters_sidoAreas_success() {
43-
// // given
44-
// double south = 37.0, north = 38.0, west = 126.0, east = 127.0;
45-
// int zoom = 9;
46-
//
47-
// given(sidoAreasRepository.findSidoAreaClusters(south, north, west, east))
48-
// .willReturn(AreaClusterFixture.sidoAreaList());
49-
//
50-
// // when
51-
// List<DiaryClusterResponseDto> result = mapService.getDiaryClusters(south, north, west, east, zoom);
52-
//
53-
// // then
54-
// assertThat(result).hasSize(2);
55-
// assertThat(result.getFirst().areaName()).isEqualTo("서울특별시");
56-
// assertThat(result.getFirst().diaryCount()).isEqualTo(100L);
57-
// verify(sidoAreasRepository).findSidoAreaClusters(south, north, west, east);
58-
// }
59-
//
60-
// @DisplayName("성공 테스트: 줌레벨이 11 이상이면 시군구 클러스터 조회")
61-
// @Test
62-
// void getDiaryClusters_siggAreas_success() {
63-
// // given
64-
// double south = 37.0, north = 38.0, west = 126.0, east = 127.0;
65-
// int zoom = 12;
66-
//
67-
// given(siggAreasRepository.findSiggAreaClusters(south, north, west, east))
68-
// .willReturn(AreaClusterFixture.siggAreaList());
69-
//
70-
// // when
71-
// List<DiaryClusterResponseDto> result = mapService.getDiaryClusters(south, north, west, east, zoom);
72-
//
73-
// // then
74-
// assertThat(result).hasSize(2);
75-
// assertThat(result.get(1).areaName()).isEqualTo("송파구");
76-
// assertThat(result.get(1).diaryCount()).isEqualTo(30L);
77-
// verify(siggAreasRepository).findSiggAreaClusters(south, north, west, east);
78-
// }
79-
80-
@DisplayName("성공 테스트: 마커 조회 (줌 14 이상)")
50+
@Mock
51+
private DiaryCacheDao diaryCacheDao;
52+
53+
@Mock
54+
private DiaryService diaryService;
55+
56+
@Mock
57+
private DiaryGeohashService diaryGeohashService;
58+
59+
@Mock
60+
private ClusterCacheDao clusterCacheDao;
61+
62+
private static final String GEOHASH = "abc";
63+
private static final int VALID_LEVEL_1 = 1;
64+
private static final int VALID_LEVEL_2 = 2;
65+
66+
private final List<DiaryClusterResponseDto> mockResult = List.of(
67+
new DiaryClusterResponseDto("서울", 1L, 37.5665, 126.9780, 10L)
68+
);
69+
70+
@DisplayName("성공: 클러스터 캐시 HIT")
71+
@Test
72+
void getDiaryClusters_success_cacheHit() {
73+
// given
74+
given(clusterCacheDao.getDiaryCluster(GEOHASH, VALID_LEVEL_1)).willReturn(Optional.of(mockResult));
75+
76+
// when
77+
List<DiaryClusterResponseDto> result = mapService.getDiaryClusters(GEOHASH, VALID_LEVEL_1);
78+
79+
// then
80+
assertThat(result).isEqualTo(mockResult);
81+
verify(sidoAreasRepository, never()).findByGeohashPrefix(any());
82+
verify(clusterCacheDao, never()).setDiaryCluster(any(), anyInt(), any(), any());
83+
}
84+
85+
@DisplayName("성공: 캐시 MISS → DB 조회 → 캐시 저장")
86+
@Test
87+
void getDiaryClusters_success_cacheMiss_then_dbHit_and_cacheStore() {
88+
// given
89+
given(clusterCacheDao.getDiaryCluster(GEOHASH, VALID_LEVEL_1)).willReturn(Optional.empty());
90+
given(sidoAreasRepository.findByGeohashPrefix(GEOHASH)).willReturn(mockResult);
91+
92+
// when
93+
List<DiaryClusterResponseDto> result = mapService.getDiaryClusters(GEOHASH, VALID_LEVEL_1);
94+
95+
// then
96+
assertThat(result).isEqualTo(mockResult);
97+
verify(clusterCacheDao).setDiaryCluster(eq(GEOHASH), eq(VALID_LEVEL_1), eq(mockResult), any());
98+
}
99+
100+
@DisplayName("실패: 유효하지 않은 level")
101+
@Test
102+
void getDiaryClusters_invalidLevel() {
103+
// given
104+
int invalidLevel = 99;
105+
106+
// expect
107+
assertThatThrownBy(() -> mapService.getDiaryClusters(GEOHASH, invalidLevel))
108+
.isInstanceOf(InvalidMapLevelException.class);
109+
}
110+
111+
@DisplayName("실패: geohash 길이 불일치")
112+
@Test
113+
void getDiaryClusters_invalidGeohashLength() {
114+
// given
115+
String invalidGeohash = "abcd";
116+
117+
// expect
118+
assertThatThrownBy(() -> mapService.getDiaryClusters(invalidGeohash, VALID_LEVEL_1))
119+
.isInstanceOf(InvalidGeohashException.class)
120+
.hasMessageContaining("geohash 길이가 유효하지 않습니다");
121+
}
122+
123+
@DisplayName("성공 : geohash 캐시 HIT + 모든 diary 캐시 HIT")
124+
@Test
125+
void getDiariesByGeohash_success_allCacheHit() {
126+
// given
127+
String geohash = "wydmt";
128+
Set<Long> cachedIds = Set.of(1L, 2L);
129+
DiaryMarkerResponseDto dto1 = DiaryMarkerFixture.createDiaryMarker(1L);
130+
DiaryMarkerResponseDto dto2 = DiaryMarkerFixture.createDiaryMarker(2L);
131+
132+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
133+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
134+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(dto2);
135+
136+
// when
137+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
138+
139+
// then
140+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
141+
verify(diaryRepository, never()).findAllById(any());
142+
verify(diaryGeohashService, never()).getDiaryIdsByGeohash(any());
143+
}
144+
145+
@DisplayName("성공 : geohash 캐시 HIT + 모든 diary 캐시 MISS")
146+
@Test
147+
void getDiariesByGeohash_success_allDiaryCacheMiss() {
148+
// given
149+
String geohash = "wydmt";
150+
Set<Long> cachedIds = Set.of(1L, 2L);
151+
Diary diary1 = DiaryFixture.createDiaryFixture(1L);
152+
Diary diary2 = DiaryFixture.createDiaryFixture(2L);
153+
DiaryMarkerResponseDto dto1 = DiaryMarkerResponseDto.of(diary1);
154+
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
155+
156+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
157+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(null);
158+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
159+
given(diaryService.getDiaries(List.of(1L, 2L))).willReturn(List.of(diary1, diary2));
160+
161+
// when
162+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
163+
164+
// then
165+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
166+
verify(diaryCacheDao).cacheDiary(1L, dto1);
167+
verify(diaryCacheDao).cacheDiary(2L, dto2);
168+
}
169+
170+
@DisplayName("성공 : geohash 캐시 HIT + 일부 diary 캐시 MISS")
171+
@Test
172+
void getDiariesByGeohash_success_partialDiaryCacheMiss() {
173+
// given
174+
String geohash = "wydmt";
175+
Set<Long> cachedIds = Set.of(1L, 2L);
176+
DiaryMarkerResponseDto dto1 = DiaryMarkerFixture.createDiaryMarker(1L);
177+
Diary diary2 = DiaryFixture.createDiaryFixture(2L);
178+
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
179+
180+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
181+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
182+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
183+
given(diaryService.getDiaries(List.of(2L))).willReturn(List.of(diary2));
184+
185+
// when
186+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
187+
188+
// then
189+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
190+
verify(diaryService).getDiaries(List.of(2L));
191+
}
192+
193+
@DisplayName("성공 : geohash 캐시 MISS → DB 조회 → 모든 diary 캐시 HIT")
194+
@Test
195+
void getDiariesByGeohash_success_geohashMiss_allDiaryCacheHit() {
196+
// given
197+
String geohash = "wydmt";
198+
List<Long> diaryIdsFromDb = List.of(1L, 2L);
199+
DiaryMarkerResponseDto dto1 = DiaryMarkerFixture.createDiaryMarker(1L);
200+
DiaryMarkerResponseDto dto2 = DiaryMarkerFixture.createDiaryMarker(2L);
201+
202+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
203+
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(diaryIdsFromDb);
204+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
205+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(dto2);
206+
207+
// when
208+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
209+
210+
// then
211+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
212+
verify(diaryCacheDao).cacheDiaryIdSetByGeohash(geohash, diaryIdsFromDb);
213+
verify(diaryService, never()).getDiaries(any());
214+
}
215+
216+
@DisplayName("성공 : geohash 캐시 MISS → DB 조회 → 모든 diary 캐시 MISS")
217+
@Test
218+
void getDiariesByGeohash_success_geohashMiss_allDiaryCacheMiss() {
219+
// given
220+
String geohash = "wydmt";
221+
List<Long> diaryIdsFromDb = List.of(1L, 2L);
222+
Diary diary1 = DiaryFixture.createDiaryFixture(1L);
223+
Diary diary2 = DiaryFixture.createDiaryFixture(2L);
224+
DiaryMarkerResponseDto dto1 = DiaryMarkerResponseDto.of(diary1);
225+
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
226+
227+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
228+
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(diaryIdsFromDb);
229+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(null);
230+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
231+
given(diaryService.getDiaries(List.of(1L, 2L))).willReturn(List.of(diary1, diary2));
232+
233+
// when
234+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
235+
236+
// then
237+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
238+
verify(diaryCacheDao).cacheDiaryIdSetByGeohash(geohash, diaryIdsFromDb);
239+
verify(diaryCacheDao).cacheDiary(1L, dto1);
240+
verify(diaryCacheDao).cacheDiary(2L, dto2);
241+
}
242+
243+
@DisplayName("성공 : geohash 캐시 MISS → DB 조회 → diary 일부 캐시 MISS")
81244
@Test
82-
void getDiariesInBounds_success() {
245+
void getDiariesByGeohash_success_geohashCacheMissAndDiaryCacheMiss() {
83246
// given
84-
double south = 37.0, north = 38.0, west = 126.0, east = 127.0;
85-
List<DiaryMarkerResponseDto> markers = DiaryMarkerFixture.createDiaryMarkers();
247+
String geohash = "abcde";
248+
List<Long> dbIds = List.of(1L, 2L);
249+
DiaryMarkerResponseDto dto1 = DiaryMarkerFixture.createDiaryMarker(1L);
250+
Diary diary2 = DiaryFixture.createDiaryFixture(2L);
251+
DiaryMarkerResponseDto dto2 = DiaryMarkerResponseDto.of(diary2);
252+
253+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
254+
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(dbIds);
255+
given(diaryCacheDao.getDiaryFromCache(1L)).willReturn(dto1);
256+
given(diaryCacheDao.getDiaryFromCache(2L)).willReturn(null);
257+
given(diaryService.getDiaries(List.of(2L))).willReturn(List.of(diary2));
258+
259+
// when
260+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
86261

87-
given(diaryRepository.findDiariesInBounds(south, north, west, east))
88-
.willReturn(markers);
262+
// then
263+
assertThat(result).containsExactlyInAnyOrder(dto1, dto2);
264+
verify(diaryCacheDao).cacheDiaryIdSetByGeohash(geohash, dbIds);
265+
}
266+
267+
@DisplayName("성공 : Redis 예외 발생 시 fallback 작동: DB에서 조회됨")
268+
@Test
269+
void getDiariesByGeohash_redisFailureHandledInternally() {
270+
// given
271+
String geohash = "abcde";
272+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(Collections.emptySet());
273+
274+
List<Long> diaryIds = List.of(1L);
275+
Diary diary = DiaryFixture.createDiaryFixture(1L);
276+
given(diaryGeohashService.getDiaryIdsByGeohash(geohash)).willReturn(diaryIds);
277+
given(diaryService.getDiaries(diaryIds)).willReturn(List.of(diary));
278+
279+
// when
280+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
281+
282+
// then
283+
assertThat(result).hasSize(1);
284+
assertThat(result.getFirst().diaryId()).isEqualTo(1L);
285+
verify(diaryService).getDiaries(diaryIds);
286+
}
287+
288+
@DisplayName("예외 : diaryId 존재하나 DB에 diary 없음 ")
289+
@Test
290+
void diaryIdExistsButDiaryMissingInDb() {
291+
// given
292+
String geohash = "wydmt";
293+
Set<Long> cachedIds = Set.of(100L);
294+
given(diaryCacheDao.getDiaryIdSetFromCache(geohash)).willReturn(cachedIds);
295+
given(diaryCacheDao.getDiaryFromCache(100L)).willReturn(null); // cache miss
296+
given(diaryService.getDiaries(List.of(100L))).willReturn(Collections.emptyList());
89297

90298
// when
91-
List<DiaryMarkerResponseDto> result = mapService.getDiariesInBounds(south, north, west, east);
299+
List<DiaryMarkerResponseDto> result = mapService.getDiariesByGeohash(geohash);
92300

93301
// then
94-
assertThat(result).hasSize(2);
95-
assertThat(result.get(0).title()).isEqualTo("첫번째 다이어리");
96-
assertThat(result.get(1).likeCount()).isEqualTo(7L);
97-
verify(diaryRepository).findDiariesInBounds(south, north, west, east);
302+
assertThat(result).isEmpty();
98303
}
99304

100305
}

src/test/java/com/example/log4u/fixture/DiaryFixture.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ public static Diary createDiaryFixture() {
2828
.build();
2929
}
3030

31+
public static Diary createDiaryFixture(Long id) {
32+
return Diary.builder()
33+
.diaryId(id)
34+
.userId(1L)
35+
.title("테스트 다이어리")
36+
.thumbnailUrl("thumbnail.jpg")
37+
.content("다이어리 내용입니다.")
38+
.diaryDate(LocalDate.now())
39+
.location(LocationFixture.createDefaultLocation())
40+
.likeCount(11L)
41+
.build();
42+
}
43+
3144
// 커스텀 다이어리 생성
3245
public static Diary createCustomDiaryFixture(
3346
Long diaryId,

src/test/java/com/example/log4u/fixture/DiaryMarkerFixture.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,16 @@ public static List<DiaryMarkerResponseDto> createDiaryMarkers() {
1717
createDiaryMarker(2L, "두번째 다이어리", "https://example.com/thumb2.jpg", 7L, 37.5678, 126.9900, LocalDateTime.now().minusDays(2))
1818
);
1919
}
20+
21+
public static DiaryMarkerResponseDto createDiaryMarker(Long id) {
22+
return createDiaryMarker(
23+
id,
24+
"다이어리 " + id,
25+
"https://example.com/thumb" + id + ".jpg",
26+
10L + id, // 예시로 likeCount 변화
27+
37.5 + id * 0.01,
28+
126.9 + id * 0.01,
29+
LocalDateTime.now().minusDays(id)
30+
);
31+
}
2032
}

0 commit comments

Comments
 (0)