Skip to content

Commit d5a9f49

Browse files
authored
test: WebSocket 관련 테스트 코드 작성 (#440)
* test: WebSocket - STOMP 통합 테스트 작성 * test: 메시지 송신 테스트 * chore: 코드 리포매팅 * chore: Mockito -> BDDMockito로 변경
1 parent 51d1d06 commit d5a9f49

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import com.example.solidconnection.chat.domain.ChatReadStatus;
1313
import com.example.solidconnection.chat.domain.ChatRoom;
1414
import com.example.solidconnection.chat.dto.ChatMessageResponse;
15+
import com.example.solidconnection.chat.dto.ChatMessageSendRequest;
16+
import com.example.solidconnection.chat.dto.ChatMessageSendResponse;
1517
import com.example.solidconnection.chat.dto.ChatRoomListResponse;
1618
import com.example.solidconnection.chat.fixture.ChatAttachmentFixture;
1719
import com.example.solidconnection.chat.fixture.ChatMessageFixture;
@@ -29,10 +31,14 @@
2931
import org.junit.jupiter.api.DisplayName;
3032
import org.junit.jupiter.api.Nested;
3133
import org.junit.jupiter.api.Test;
34+
import org.mockito.ArgumentCaptor;
35+
import org.mockito.BDDMockito;
3236
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.boot.test.mock.mockito.MockBean;
3338
import org.springframework.data.domain.PageRequest;
3439
import org.springframework.data.domain.Pageable;
3540
import org.springframework.data.domain.Sort;
41+
import org.springframework.messaging.simp.SimpMessagingTemplate;
3642

3743
@TestContainerSpringBootTest
3844
@DisplayName("채팅 서비스 테스트")
@@ -62,6 +68,9 @@ class ChatServiceTest {
6268
@Autowired
6369
private ChatAttachmentFixture chatAttachmentFixture;
6470

71+
@MockBean
72+
private SimpMessagingTemplate simpMessagingTemplate;
73+
6574
private SiteUser user;
6675
private SiteUser mentor1;
6776
private SiteUser mentor2;
@@ -363,4 +372,53 @@ void setUp() {
363372
.hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
364373
}
365374
}
375+
376+
@Nested
377+
class 채팅_메시지를_전송한다 {
378+
379+
private SiteUser sender;
380+
private ChatParticipant senderParticipant;
381+
private ChatRoom chatRoom;
382+
383+
@BeforeEach
384+
void setUp() {
385+
sender = siteUserFixture.사용자(111, "sender");
386+
chatRoom = chatRoomFixture.채팅방(false);
387+
senderParticipant = chatParticipantFixture.참여자(sender.getId(), chatRoom);
388+
}
389+
390+
@Test
391+
void 채팅방_참여자는_메시지를_전송할_수_있다() {
392+
// given
393+
final String content = "안녕하세요";
394+
ChatMessageSendRequest request = new ChatMessageSendRequest(content);
395+
396+
// when
397+
chatService.sendChatMessage(request, sender.getId(), chatRoom.getId());
398+
399+
// then
400+
ArgumentCaptor<String> destinationCaptor = ArgumentCaptor.forClass(String.class);
401+
ArgumentCaptor<ChatMessageSendResponse> payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class);
402+
403+
BDDMockito.verify(simpMessagingTemplate).convertAndSend(destinationCaptor.capture(), payloadCaptor.capture());
404+
405+
assertAll(
406+
() -> assertThat(destinationCaptor.getValue()).isEqualTo("/topic/chat/" + chatRoom.getId()),
407+
() -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content),
408+
() -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId())
409+
);
410+
}
411+
412+
@Test
413+
void 채팅_참여자가_아니면_메시지를_전송할_수_없다() {
414+
// given
415+
SiteUser nonParticipant = siteUserFixture.사용자(333, "nonParticipant");
416+
ChatMessageSendRequest request = new ChatMessageSendRequest("안녕하세요");
417+
418+
// when & then
419+
assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId()))
420+
.isInstanceOf(CustomException.class)
421+
.hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage());
422+
}
423+
}
366424
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.example.solidconnection.websocket;
2+
3+
import static java.util.concurrent.TimeUnit.SECONDS;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.assertj.core.api.ThrowableAssert.catchThrowable;
6+
import static org.junit.jupiter.api.Assertions.assertAll;
7+
8+
import com.example.solidconnection.auth.service.AccessToken;
9+
import com.example.solidconnection.auth.service.AuthTokenProvider;
10+
import com.example.solidconnection.siteuser.domain.SiteUser;
11+
import com.example.solidconnection.siteuser.fixture.SiteUserFixture;
12+
import com.example.solidconnection.support.TestContainerSpringBootTest;
13+
import java.util.List;
14+
import java.util.concurrent.ArrayBlockingQueue;
15+
import java.util.concurrent.BlockingQueue;
16+
import java.util.concurrent.ExecutionException;
17+
import org.junit.jupiter.api.AfterEach;
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.DisplayName;
20+
import org.junit.jupiter.api.Nested;
21+
import org.junit.jupiter.api.Test;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.boot.test.web.server.LocalServerPort;
24+
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
25+
import org.springframework.messaging.simp.stomp.StompHeaders;
26+
import org.springframework.messaging.simp.stomp.StompSession;
27+
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
28+
import org.springframework.web.client.HttpClientErrorException;
29+
import org.springframework.web.socket.WebSocketHttpHeaders;
30+
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
31+
import org.springframework.web.socket.messaging.WebSocketStompClient;
32+
import org.springframework.web.socket.sockjs.client.SockJsClient;
33+
import org.springframework.web.socket.sockjs.client.Transport;
34+
import org.springframework.web.socket.sockjs.client.WebSocketTransport;
35+
36+
@TestContainerSpringBootTest
37+
@DisplayName("WebSocket/STOMP 통합 테스트")
38+
class WebSocketStompIntegrationTest {
39+
40+
@LocalServerPort
41+
private int port;
42+
private String url;
43+
private WebSocketStompClient stompClient;
44+
private StompSession stompSession;
45+
46+
@Autowired
47+
private AuthTokenProvider authTokenProvider;
48+
49+
@Autowired
50+
private SiteUserFixture siteUserFixture;
51+
52+
@BeforeEach
53+
void setUp() {
54+
this.url = String.format("ws://localhost:%d/connect", port);
55+
List<Transport> transports = List.of(new WebSocketTransport(new StandardWebSocketClient()));
56+
this.stompClient = new WebSocketStompClient(new SockJsClient(transports));
57+
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
58+
}
59+
60+
@AfterEach
61+
void tearDown() {
62+
if (this.stompSession != null && this.stompSession.isConnected()) {
63+
this.stompSession.disconnect();
64+
}
65+
}
66+
67+
@Nested
68+
class WebSocket_핸드셰이크_및_STOMP_세션_수립_테스트 {
69+
70+
private final BlockingQueue<Throwable> transportErrorQueue = new ArrayBlockingQueue<>(1);
71+
72+
private final StompSessionHandlerAdapter sessionHandler = new StompSessionHandlerAdapter() {
73+
@Override
74+
public void handleTransportError(StompSession session, Throwable exception) {
75+
transportErrorQueue.add(exception);
76+
}
77+
};
78+
79+
@Test
80+
void 인증된_사용자는_핸드셰이크를_성공한다() throws Exception {
81+
// given
82+
SiteUser user = siteUserFixture.사용자();
83+
AccessToken accessToken = authTokenProvider.generateAccessToken(authTokenProvider.toSubject(user), user.getRole());
84+
85+
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
86+
handshakeHeaders.add("Authorization", "Bearer " + accessToken.token());
87+
88+
// when
89+
stompSession = stompClient.connectAsync(url, handshakeHeaders, new StompHeaders(), sessionHandler).get(5, SECONDS);
90+
91+
// then
92+
assertAll(
93+
() -> assertThat(stompSession).isNotNull(),
94+
() -> assertThat(transportErrorQueue).isEmpty()
95+
);
96+
}
97+
98+
@Test
99+
void 인증되지_않은_사용자는_핸드셰이크를_실패한다() {
100+
// when
101+
Throwable thrown = catchThrowable(() -> {
102+
stompSession = stompClient.connectAsync(url, new WebSocketHttpHeaders(), new StompHeaders(), sessionHandler).get(5, SECONDS);
103+
});
104+
105+
// then
106+
assertAll(
107+
() -> assertThat(thrown)
108+
.isInstanceOf(ExecutionException.class)
109+
.hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class),
110+
() -> assertThat(transportErrorQueue).hasSize(1)
111+
);
112+
}
113+
}
114+
}

0 commit comments

Comments
 (0)