Skip to content

Commit f5a6e25

Browse files
committed
test: RankingServiceTest 클래스 추가 및 조회, 좋아요, 주문 점수 증가 기능 테스트 구현
1 parent ff536c6 commit f5a6e25

1 file changed

Lines changed: 203 additions & 0 deletions

File tree

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package com.loopers.domain.ranking;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.DisplayName;
5+
import org.junit.jupiter.api.Nested;
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.ExtendWith;
8+
import org.mockito.Mock;
9+
import org.mockito.junit.jupiter.MockitoExtension;
10+
import org.springframework.data.redis.core.RedisTemplate;
11+
import org.springframework.data.redis.core.ZSetOperations;
12+
13+
import java.time.LocalDate;
14+
import java.util.LinkedHashSet;
15+
import java.util.Set;
16+
17+
import static org.mockito.ArgumentMatchers.*;
18+
import static org.mockito.Mockito.*;
19+
20+
@ExtendWith(MockitoExtension.class)
21+
class RankingServiceTest {
22+
23+
@Mock
24+
private RedisTemplate<String, String> redisTemplate;
25+
26+
@Mock
27+
private ZSetOperations<String, String> zSetOperations;
28+
29+
@Mock
30+
private RankingWeight rankingWeight;
31+
32+
private RankingService rankingService;
33+
34+
@BeforeEach
35+
void setUp() {
36+
when(redisTemplate.opsForZSet()).thenReturn(zSetOperations);
37+
rankingService = new RankingService(redisTemplate, rankingWeight);
38+
}
39+
40+
@Nested
41+
@DisplayName("조회 점수 증가")
42+
class IncrementViewScore {
43+
44+
@Test
45+
@DisplayName("조회 이벤트 발생 시 ZSET에 가중치 점수가 추가된다")
46+
void shouldIncrementScoreWithViewWeight() {
47+
// given
48+
Long productId = 100L;
49+
LocalDate date = LocalDate.of(2025, 1, 15);
50+
String expectedKey = "ranking:all:20250115";
51+
52+
when(rankingWeight.calculateViewScore()).thenReturn(0.1);
53+
when(redisTemplate.hasKey(expectedKey)).thenReturn(true);
54+
55+
// when
56+
rankingService.incrementViewScore(productId, date);
57+
58+
// then
59+
verify(zSetOperations).incrementScore(expectedKey, "100", 0.1);
60+
}
61+
62+
@Test
63+
@DisplayName("새로운 키가 생성되면 TTL이 설정된다")
64+
void shouldSetTtlWhenKeyIsNew() {
65+
// given
66+
Long productId = 100L;
67+
LocalDate date = LocalDate.of(2025, 1, 15);
68+
String expectedKey = "ranking:all:20250115";
69+
70+
when(rankingWeight.calculateViewScore()).thenReturn(0.1);
71+
when(redisTemplate.hasKey(expectedKey)).thenReturn(false);
72+
73+
// when
74+
rankingService.incrementViewScore(productId, date);
75+
76+
// then
77+
verify(redisTemplate).expire(eq(expectedKey), any());
78+
}
79+
}
80+
81+
@Nested
82+
@DisplayName("좋아요 점수 증가")
83+
class UpdateLikeScore {
84+
85+
@Test
86+
@DisplayName("좋아요 이벤트 발생 시 양수 점수가 추가된다")
87+
void shouldIncrementScoreWhenLiked() {
88+
// given
89+
Long productId = 100L;
90+
LocalDate date = LocalDate.of(2025, 1, 15);
91+
92+
when(rankingWeight.calculateLikeScore(true)).thenReturn(0.2);
93+
when(redisTemplate.hasKey(anyString())).thenReturn(true);
94+
95+
// when
96+
rankingService.updateLikeScore(productId, true, date);
97+
98+
// then
99+
verify(zSetOperations).incrementScore(anyString(), eq("100"), eq(0.2));
100+
}
101+
102+
@Test
103+
@DisplayName("좋아요 취소 이벤트 발생 시 음수 점수가 추가된다")
104+
void shouldDecrementScoreWhenUnliked() {
105+
// given
106+
Long productId = 100L;
107+
LocalDate date = LocalDate.of(2025, 1, 15);
108+
109+
when(rankingWeight.calculateLikeScore(false)).thenReturn(-0.2);
110+
when(redisTemplate.hasKey(anyString())).thenReturn(true);
111+
112+
// when
113+
rankingService.updateLikeScore(productId, false, date);
114+
115+
// then
116+
verify(zSetOperations).incrementScore(anyString(), eq("100"), eq(-0.2));
117+
}
118+
}
119+
120+
@Nested
121+
@DisplayName("주문 점수 증가")
122+
class IncrementOrderScore {
123+
124+
@Test
125+
@DisplayName("주문 수량 기반으로 점수가 계산된다")
126+
void shouldCalculateScoreBasedOnQuantity() {
127+
// given
128+
Long productId = 100L;
129+
int quantity = 5;
130+
LocalDate date = LocalDate.of(2025, 1, 15);
131+
132+
when(rankingWeight.calculateOrderScore(quantity)).thenReturn(3.5); // 0.7 * 5
133+
when(redisTemplate.hasKey(anyString())).thenReturn(true);
134+
135+
// when
136+
rankingService.incrementOrderScore(productId, quantity, date);
137+
138+
// then
139+
verify(zSetOperations).incrementScore(anyString(), eq("100"), eq(3.5));
140+
}
141+
}
142+
143+
@Nested
144+
@DisplayName("전날 점수 다음날 이관")
145+
class CarryOverScores {
146+
147+
@Test
148+
@DisplayName("전날 점수의 일부가 다음날 키로 복사된다")
149+
void shouldCopyScoresWithWeight() {
150+
// given
151+
LocalDate today = LocalDate.of(2025, 1, 15);
152+
LocalDate tomorrow = LocalDate.of(2025, 1, 16);
153+
String fromKey = "ranking:all:20250115";
154+
String toKey = "ranking:all:20250116";
155+
156+
Set<ZSetOperations.TypedTuple<String>> tuples = new LinkedHashSet<>();
157+
tuples.add(createTuple("100", 10.0));
158+
tuples.add(createTuple("200", 5.0));
159+
160+
when(zSetOperations.zCard(toKey)).thenReturn(0L);
161+
when(zSetOperations.rangeWithScores(fromKey, 0, -1)).thenReturn(tuples);
162+
163+
// when
164+
rankingService.carryOverScores(today, tomorrow, 0.1);
165+
166+
// then
167+
verify(zSetOperations).add(toKey, "100", 1.0); // 10.0 * 0.1
168+
verify(zSetOperations).add(toKey, "200", 0.5); // 5.0 * 0.1
169+
verify(redisTemplate).expire(eq(toKey), any());
170+
}
171+
172+
@Test
173+
@DisplayName("이미 준비된 랭킹이 있으면 스킵한다")
174+
void shouldSkipIfAlreadyPrepared() {
175+
// given
176+
LocalDate today = LocalDate.of(2025, 1, 15);
177+
LocalDate tomorrow = LocalDate.of(2025, 1, 16);
178+
String toKey = "ranking:all:20250116";
179+
180+
when(zSetOperations.zCard(toKey)).thenReturn(10L); // 이미 데이터 존재
181+
182+
// when
183+
rankingService.carryOverScores(today, tomorrow, 0.1);
184+
185+
// then
186+
verify(zSetOperations, never()).rangeWithScores(anyString(), anyLong(), anyLong());
187+
verify(zSetOperations, never()).add(anyString(), anyString(), anyDouble());
188+
}
189+
190+
private ZSetOperations.TypedTuple<String> createTuple(String value, Double score) {
191+
return new ZSetOperations.TypedTuple<>() {
192+
@Override
193+
public String getValue() { return value; }
194+
@Override
195+
public Double getScore() { return score; }
196+
@Override
197+
public int compareTo(ZSetOperations.TypedTuple<String> o) {
198+
return Double.compare(this.getScore(), o.getScore());
199+
}
200+
};
201+
}
202+
}
203+
}

0 commit comments

Comments
 (0)