1+ package com .back .domain .notification .service ;
2+
3+ import static org .mockito .ArgumentMatchers .*;
4+ import static org .mockito .BDDMockito .*;
5+ import static org .mockito .Mockito .verify ;
6+
7+ import com .back .domain .notification .dto .NotificationWebSocketDto ;
8+ import com .back .domain .notification .entity .NotificationType ;
9+ import org .junit .jupiter .api .BeforeEach ;
10+ import org .junit .jupiter .api .DisplayName ;
11+ import org .junit .jupiter .api .Nested ;
12+ import org .junit .jupiter .api .Test ;
13+ import org .junit .jupiter .api .extension .ExtendWith ;
14+ import org .mockito .InjectMocks ;
15+ import org .mockito .Mock ;
16+ import org .mockito .junit .jupiter .MockitoExtension ;
17+ import org .springframework .messaging .simp .SimpMessagingTemplate ;
18+
19+ import java .time .LocalDateTime ;
20+
21+ @ ExtendWith (MockitoExtension .class )
22+ class NotificationWebSocketServiceTest {
23+
24+ @ Mock
25+ private SimpMessagingTemplate messagingTemplate ;
26+
27+ @ InjectMocks
28+ private NotificationWebSocketService webSocketService ;
29+
30+ private NotificationWebSocketDto notificationDto ;
31+
32+ @ BeforeEach
33+ void setUp () {
34+ notificationDto = NotificationWebSocketDto .from (
35+ 1L ,
36+ "테스트 알림" ,
37+ "알림 내용" ,
38+ NotificationType .PERSONAL ,
39+ "/target" ,
40+ LocalDateTime .now ()
41+ );
42+ }
43+
44+ @ Nested
45+ @ DisplayName ("개인 알림 전송 테스트" )
46+ class SendNotificationToUserTest {
47+
48+ @ Test
49+ @ DisplayName ("특정 유저에게 알림 전송" )
50+ void t1 () {
51+ // given
52+ Long userId = 1L ;
53+ String expectedDestination = "/topic/user/1/notifications" ;
54+
55+ // when
56+ webSocketService .sendNotificationToUser (userId , notificationDto );
57+
58+ // then
59+ verify (messagingTemplate ).convertAndSend (
60+ eq (expectedDestination ),
61+ eq (notificationDto )
62+ );
63+ }
64+
65+ @ Test
66+ @ DisplayName ("전송 실패 시 예외를 로깅하고 정상 종료" )
67+ void t2 () {
68+ // given
69+ Long userId = 1L ;
70+ willThrow (new RuntimeException ("전송 실패" ))
71+ .given (messagingTemplate )
72+ .convertAndSend (anyString (), any (Object .class ));
73+
74+ // when & then - 예외가 발생해도 메서드는 정상 종료되어야 함
75+ webSocketService .sendNotificationToUser (userId , notificationDto );
76+
77+ verify (messagingTemplate ).convertAndSend (anyString (), any (Object .class ));
78+ }
79+ }
80+
81+ @ Nested
82+ @ DisplayName ("시스템 알림 브로드캐스트 테스트" )
83+ class BroadcastSystemNotificationTest {
84+
85+ @ Test
86+ @ DisplayName ("전체 유저에게 시스템 알림 브로드캐스트" )
87+ void t1 () {
88+ // given
89+ String expectedDestination = "/topic/notifications/system" ;
90+ NotificationWebSocketDto systemDto = NotificationWebSocketDto .from (
91+ 2L ,
92+ "시스템 알림" ,
93+ "시스템 공지" ,
94+ NotificationType .SYSTEM ,
95+ "/system" ,
96+ LocalDateTime .now ()
97+ );
98+
99+ // when
100+ webSocketService .broadcastSystemNotification (systemDto );
101+
102+ // then
103+ verify (messagingTemplate ).convertAndSend (
104+ eq (expectedDestination ),
105+ eq (systemDto )
106+ );
107+ }
108+
109+ @ Test
110+ @ DisplayName ("브로드캐스트 실패 시 예외 로깅하고 정상 종료" )
111+ void t2 () {
112+ // given
113+ willThrow (new RuntimeException ("브로드캐스트 실패" ))
114+ .given (messagingTemplate )
115+ .convertAndSend (anyString (), any (Object .class ));
116+
117+ // when & then
118+ webSocketService .broadcastSystemNotification (notificationDto );
119+
120+ verify (messagingTemplate ).convertAndSend (anyString (), any (Object .class ));
121+ }
122+ }
123+
124+ @ Nested
125+ @ DisplayName ("스터디룸 알림 전송 테스트" )
126+ class SendNotificationToRoomTest {
127+
128+ @ Test
129+ @ DisplayName ("스터디룸 멤버들에게 알림 전송" )
130+ void t1 () {
131+ // given
132+ Long roomId = 100L ;
133+ String expectedDestination = "/topic/room/100/notifications" ;
134+ NotificationWebSocketDto roomDto = NotificationWebSocketDto .from (
135+ 3L ,
136+ "스터디룸 알림" ,
137+ "새 공지사항" ,
138+ NotificationType .ROOM ,
139+ "/room/100" ,
140+ LocalDateTime .now ()
141+ );
142+
143+ // when
144+ webSocketService .sendNotificationToRoom (roomId , roomDto );
145+
146+ // then
147+ verify (messagingTemplate ).convertAndSend (
148+ eq (expectedDestination ),
149+ eq (roomDto )
150+ );
151+ }
152+
153+ @ Test
154+ @ DisplayName ("전송 실패 시 예외 로깅하고 정상 종료" )
155+ void t2 () {
156+ // given
157+ Long roomId = 100L ;
158+ willThrow (new RuntimeException ("룸 전송 실패" ))
159+ .given (messagingTemplate )
160+ .convertAndSend (anyString (), any (Object .class ));
161+
162+ // when & then
163+ webSocketService .sendNotificationToRoom (roomId , notificationDto );
164+
165+ verify (messagingTemplate ).convertAndSend (anyString (), any (Object .class ));
166+ }
167+ }
168+
169+ @ Nested
170+ @ DisplayName ("destination 검증 테스트" )
171+ class DestinationTest {
172+
173+ @ Test
174+ @ DisplayName ("유저별 알림 경로 정상 생성" )
175+ void t1 () {
176+ // given
177+ Long userId = 12345L ;
178+
179+ // when
180+ webSocketService .sendNotificationToUser (userId , notificationDto );
181+
182+ // then
183+ verify (messagingTemplate ).convertAndSend (
184+ eq ("/topic/user/12345/notifications" ),
185+ any (Object .class )
186+ );
187+ }
188+
189+ @ Test
190+ @ DisplayName ("스터디룸 알림 경로 정상 생성" )
191+ void t2 () {
192+ // given
193+ Long roomId = 99L ;
194+
195+ // when
196+ webSocketService .sendNotificationToRoom (roomId , notificationDto );
197+
198+ // then
199+ verify (messagingTemplate ).convertAndSend (
200+ eq ("/topic/room/99/notifications" ),
201+ any (Object .class )
202+ );
203+ }
204+
205+ @ Test
206+ @ DisplayName ("시스템 알림 경로 정상 생성" )
207+ void t3 () {
208+ // when
209+ webSocketService .broadcastSystemNotification (notificationDto );
210+
211+ // then
212+ verify (messagingTemplate ).convertAndSend (
213+ eq ("/topic/notifications/system" ),
214+ any (Object .class )
215+ );
216+ }
217+ }
218+ }
0 commit comments