Skip to content

Commit 9c3b0e0

Browse files
committed
test: 상품 상세 조회 시 랭킹 정보 테스트 추가
- ProductFacadeTest: 랭킹 포함/미포함 단위 테스트 - ProductV1ApiE2ETest: 랭킹 정보 E2E 테스트
1 parent 836b809 commit 9c3b0e0

2 files changed

Lines changed: 368 additions & 1 deletion

File tree

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package com.loopers.application.product;
2+
3+
import com.loopers.application.event.product.ProductViewedEvent;
4+
import com.loopers.domain.common.vo.Money;
5+
import com.loopers.domain.like.service.LikeReadService;
6+
import com.loopers.domain.product.service.ProductReadService;
7+
import com.loopers.domain.product.vo.Stock;
8+
import com.loopers.infrastructure.cache.ProductDetailCache;
9+
import com.loopers.infrastructure.cache.ProductListCache;
10+
import com.loopers.infrastructure.cache.ProductRankingCache;
11+
import org.junit.jupiter.api.DisplayName;
12+
import org.junit.jupiter.api.Nested;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.mockito.ArgumentCaptor;
16+
import org.mockito.InjectMocks;
17+
import org.mockito.Mock;
18+
import org.mockito.junit.jupiter.MockitoExtension;
19+
import org.springframework.context.ApplicationEventPublisher;
20+
21+
import java.util.Optional;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.mockito.ArgumentMatchers.any;
25+
import static org.mockito.ArgumentMatchers.anyLong;
26+
import static org.mockito.Mockito.*;
27+
28+
@ExtendWith(MockitoExtension.class)
29+
class ProductFacadeTest {
30+
31+
@Mock
32+
private ProductReadService productReadService;
33+
34+
@Mock
35+
private LikeReadService likeReadService;
36+
37+
@Mock
38+
private ProductDetailCache productDetailCache;
39+
40+
@Mock
41+
private ProductListCache productListCache;
42+
43+
@Mock
44+
private ProductRankingCache productRankingCache;
45+
46+
@Mock
47+
private ApplicationEventPublisher eventPublisher;
48+
49+
@InjectMocks
50+
private ProductFacade productFacade;
51+
52+
private static final Long PRODUCT_ID = 100L;
53+
private static final Long MEMBER_ID = 1L;
54+
private static final Long BRAND_ID = 10L;
55+
56+
private ProductDetailInfo createCachedProductDetailInfo() {
57+
return ProductDetailInfo.builder()
58+
.id(PRODUCT_ID)
59+
.name("테스트 상품")
60+
.description("테스트 설명")
61+
.brandId(BRAND_ID)
62+
.brandName("테스트 브랜드")
63+
.brandDescription("브랜드 설명")
64+
.price(Money.of(10000))
65+
.stock(Stock.of(100))
66+
.likeCount(50)
67+
.isLikedByMember(false)
68+
.ranking(null)
69+
.build();
70+
}
71+
72+
@DisplayName("상품 상세 조회 (getProductDetail)")
73+
@Nested
74+
class GetProductDetail {
75+
76+
@DisplayName("랭킹에 존재하는 상품이면 순위가 포함되어 반환된다")
77+
@Test
78+
void shouldIncludeRanking_whenProductIsRanked() {
79+
// given
80+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
81+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
82+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(false);
83+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(5); // 5위
84+
85+
// when
86+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
87+
88+
// then
89+
assertThat(result.getRanking()).isEqualTo(5);
90+
verify(productRankingCache).getRank(PRODUCT_ID);
91+
}
92+
93+
@DisplayName("랭킹에 존재하지 않는 상품이면 순위가 null로 반환된다")
94+
@Test
95+
void shouldReturnNullRanking_whenProductIsNotRanked() {
96+
// given
97+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
98+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
99+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(false);
100+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(null); // 순위권 밖
101+
102+
// when
103+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
104+
105+
// then
106+
assertThat(result.getRanking()).isNull();
107+
verify(productRankingCache).getRank(PRODUCT_ID);
108+
}
109+
110+
@DisplayName("랭킹 1위 상품이면 순위가 1로 반환된다")
111+
@Test
112+
void shouldReturnRanking1_whenProductIsFirstPlace() {
113+
// given
114+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
115+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
116+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(true);
117+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(1); // 1위
118+
119+
// when
120+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
121+
122+
// then
123+
assertThat(result.getRanking()).isEqualTo(1);
124+
assertThat(result.isLikedByMember()).isTrue();
125+
}
126+
127+
@DisplayName("비로그인 사용자도 랭킹 정보가 포함되어 반환된다")
128+
@Test
129+
void shouldIncludeRanking_whenUserNotLoggedIn() {
130+
// given
131+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
132+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
133+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(10); // 10위
134+
135+
// when
136+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, null);
137+
138+
// then
139+
assertThat(result.getRanking()).isEqualTo(10);
140+
assertThat(result.isLikedByMember()).isFalse();
141+
verify(likeReadService, never()).isLikedBy(anyLong(), anyLong());
142+
}
143+
144+
@DisplayName("캐시 miss 시 DB 조회 후에도 랭킹 정보가 포함된다")
145+
@Test
146+
void shouldIncludeRanking_whenCacheMiss() {
147+
// given
148+
ProductDetailInfo dbInfo = createCachedProductDetailInfo();
149+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.empty()); // 캐시 miss
150+
when(productReadService.getProductDetail(PRODUCT_ID, null)).thenReturn(dbInfo);
151+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(false);
152+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(3);
153+
154+
// when
155+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
156+
157+
// then
158+
assertThat(result.getRanking()).isEqualTo(3);
159+
verify(productDetailCache).set(PRODUCT_ID, dbInfo); // 캐시 저장 확인
160+
verify(productRankingCache).getRank(PRODUCT_ID);
161+
}
162+
163+
@DisplayName("상품 조회 이벤트가 발행된다")
164+
@Test
165+
void shouldPublishProductViewedEvent() {
166+
// given
167+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
168+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
169+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(false);
170+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(5);
171+
172+
// when
173+
productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
174+
175+
// then
176+
ArgumentCaptor<ProductViewedEvent> eventCaptor = ArgumentCaptor.forClass(ProductViewedEvent.class);
177+
verify(eventPublisher).publishEvent(eventCaptor.capture());
178+
179+
ProductViewedEvent capturedEvent = eventCaptor.getValue();
180+
assertThat(capturedEvent.productId()).isEqualTo(PRODUCT_ID);
181+
assertThat(capturedEvent.memberId()).isEqualTo(MEMBER_ID);
182+
assertThat(capturedEvent.brandId()).isEqualTo(BRAND_ID);
183+
}
184+
185+
@DisplayName("다른 필드들이 올바르게 유지된다")
186+
@Test
187+
void shouldPreserveOtherFields() {
188+
// given
189+
ProductDetailInfo cachedInfo = createCachedProductDetailInfo();
190+
when(productDetailCache.get(PRODUCT_ID)).thenReturn(Optional.of(cachedInfo));
191+
when(likeReadService.isLikedBy(MEMBER_ID, PRODUCT_ID)).thenReturn(true);
192+
when(productRankingCache.getRank(PRODUCT_ID)).thenReturn(7);
193+
194+
// when
195+
ProductDetailInfo result = productFacade.getProductDetail(PRODUCT_ID, MEMBER_ID);
196+
197+
// then
198+
assertThat(result.getId()).isEqualTo(PRODUCT_ID);
199+
assertThat(result.getName()).isEqualTo("테스트 상품");
200+
assertThat(result.getDescription()).isEqualTo("테스트 설명");
201+
assertThat(result.getBrandId()).isEqualTo(BRAND_ID);
202+
assertThat(result.getBrandName()).isEqualTo("테스트 브랜드");
203+
assertThat(result.getBrandDescription()).isEqualTo("브랜드 설명");
204+
assertThat(result.getPrice().getAmount().intValue()).isEqualTo(10000);
205+
assertThat(result.getStock().getQuantity()).isEqualTo(100);
206+
assertThat(result.getLikeCount()).isEqualTo(50);
207+
assertThat(result.isLikedByMember()).isTrue();
208+
assertThat(result.getRanking()).isEqualTo(7);
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)