Commit e89724e5cdcfc12a45bf565909d7f183ad7b9ed5
1 parent
f495f9c8
✨ feat(im): 修复单测报错的问题
Showing
5 changed files
with
154 additions
and
131 deletions
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapperTest.java
| @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | ||
| 5 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 5 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 6 | import jakarta.annotation.Resource; | 6 | import jakarta.annotation.Resource; |
| 7 | import org.junit.jupiter.api.Test; | 7 | import org.junit.jupiter.api.Test; |
| 8 | -import org.springframework.context.annotation.Import; | ||
| 9 | 8 | ||
| 10 | import java.time.LocalDateTime; | 9 | import java.time.LocalDateTime; |
| 11 | import java.util.List; | 10 | import java.util.List; |
| @@ -17,7 +16,6 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -17,7 +16,6 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 17 | * | 16 | * |
| 18 | * @author 芋道源码 | 17 | * @author 芋道源码 |
| 19 | */ | 18 | */ |
| 20 | -@Import(ImPrivateMessageMapper.class) | ||
| 21 | public class ImPrivateMessageMapperTest extends BaseDbUnitTest { | 19 | public class ImPrivateMessageMapperTest extends BaseDbUnitTest { |
| 22 | 20 | ||
| 23 | @Resource | 21 | @Resource |
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImplTest.java
| 1 | package cn.iocoder.yudao.module.im.service.message; | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 3 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 4 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 5 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 6 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 4 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 5 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 6 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 9 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 7 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; | 10 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 8 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; | 11 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; |
| 9 | import cn.iocoder.yudao.module.im.dal.redis.group.GroupReadPositionRedisDAO; | 12 | import cn.iocoder.yudao.module.im.dal.redis.group.GroupReadPositionRedisDAO; |
| 10 | -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 11 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; | 13 | import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; |
| 12 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 14 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 13 | -import cn.iocoder.yudao.module.im.service.group.ImGroupService; | ||
| 14 | import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; | 15 | import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; |
| 16 | +import cn.iocoder.yudao.module.im.service.group.ImGroupService; | ||
| 15 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | 17 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 16 | -import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 17 | import org.junit.jupiter.api.Test; | 18 | import org.junit.jupiter.api.Test; |
| 18 | -import org.junit.jupiter.api.extension.ExtendWith; | ||
| 19 | import org.mockito.InjectMocks; | 19 | import org.mockito.InjectMocks; |
| 20 | import org.mockito.Mock; | 20 | import org.mockito.Mock; |
| 21 | -import org.mockito.junit.jupiter.MockitoExtension; | ||
| 22 | 21 | ||
| 23 | import java.time.LocalDateTime; | 22 | import java.time.LocalDateTime; |
| 24 | import java.util.*; | 23 | import java.util.*; |
| @@ -28,14 +27,12 @@ import static org.junit.jupiter.api.Assertions.*; | @@ -28,14 +27,12 @@ import static org.junit.jupiter.api.Assertions.*; | ||
| 28 | import static org.mockito.ArgumentMatchers.*; | 27 | import static org.mockito.ArgumentMatchers.*; |
| 29 | import static org.mockito.Mockito.*; | 28 | import static org.mockito.Mockito.*; |
| 30 | 29 | ||
| 31 | -// DONE @芋艿:已评估,23 个测试全部通过 | ||
| 32 | /** | 30 | /** |
| 33 | * IM 群聊消息 Service 单元测试 | 31 | * IM 群聊消息 Service 单元测试 |
| 34 | * | 32 | * |
| 35 | * @author 芋道源码 | 33 | * @author 芋道源码 |
| 36 | */ | 34 | */ |
| 37 | -@ExtendWith(MockitoExtension.class) | ||
| 38 | -public class ImGroupMessageServiceImplTest { | 35 | +public class ImGroupMessageServiceImplTest extends BaseMockitoUnitTest { |
| 39 | 36 | ||
| 40 | @InjectMocks | 37 | @InjectMocks |
| 41 | private ImGroupMessageServiceImpl groupMessageService; | 38 | private ImGroupMessageServiceImpl groupMessageService; |
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImplTest.java
| @@ -2,19 +2,18 @@ package cn.iocoder.yudao.module.im.service.message; | @@ -2,19 +2,18 @@ package cn.iocoder.yudao.module.im.service.message; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.extra.spring.SpringUtil; | 3 | import cn.hutool.extra.spring.SpringUtil; |
| 4 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 4 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 5 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 6 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 5 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; | 7 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 6 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 7 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; | 9 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; |
| 8 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 10 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 9 | import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | 11 | import cn.iocoder.yudao.module.im.service.friend.ImFriendService; |
| 10 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | 12 | import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 11 | -import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 12 | import org.junit.jupiter.api.Test; | 13 | import org.junit.jupiter.api.Test; |
| 13 | -import org.junit.jupiter.api.extension.ExtendWith; | ||
| 14 | import org.mockito.InjectMocks; | 14 | import org.mockito.InjectMocks; |
| 15 | import org.mockito.Mock; | 15 | import org.mockito.Mock; |
| 16 | import org.mockito.MockedStatic; | 16 | import org.mockito.MockedStatic; |
| 17 | -import org.mockito.junit.jupiter.MockitoExtension; | ||
| 18 | 17 | ||
| 19 | import java.time.LocalDateTime; | 18 | import java.time.LocalDateTime; |
| 20 | import java.util.List; | 19 | import java.util.List; |
| @@ -29,8 +28,7 @@ import static org.mockito.Mockito.*; | @@ -29,8 +28,7 @@ import static org.mockito.Mockito.*; | ||
| 29 | * | 28 | * |
| 30 | * @author 芋道源码 | 29 | * @author 芋道源码 |
| 31 | */ | 30 | */ |
| 32 | -@ExtendWith(MockitoExtension.class) | ||
| 33 | -public class ImPrivateMessageServiceImplTest { | 31 | +public class ImPrivateMessageServiceImplTest extends BaseMockitoUnitTest { |
| 34 | 32 | ||
| 35 | @InjectMocks | 33 | @InjectMocks |
| 36 | private ImPrivateMessageServiceImpl privateMessageService; | 34 | private ImPrivateMessageServiceImpl privateMessageService; |
| @@ -155,22 +153,28 @@ public class ImPrivateMessageServiceImplTest { | @@ -155,22 +153,28 @@ public class ImPrivateMessageServiceImplTest { | ||
| 155 | 153 | ||
| 156 | @Test | 154 | @Test |
| 157 | public void testReadMessages_success() { | 155 | public void testReadMessages_success() { |
| 158 | - // 准备:mock 未读消息列表 | ||
| 159 | - List<ImPrivateMessageDO> unreadMessages = List.of( | ||
| 160 | - ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L) | ||
| 161 | - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(), | ||
| 162 | - ImPrivateMessageDO.builder().id(2L).senderId(2L).receiverId(1L) | ||
| 163 | - .status(ImMessageStatusEnum.UNREAD.getStatus()).build() | ||
| 164 | - ); | ||
| 165 | - when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L, | ||
| 166 | - ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages); | 156 | + try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) { |
| 157 | + springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class))) | ||
| 158 | + .thenReturn(privateMessageService); | ||
| 167 | 159 | ||
| 168 | - // 调用 | ||
| 169 | - privateMessageService.readPrivateMessages(1L, 2L); | 160 | + // 准备:mock 未读消息列表 |
| 161 | + List<ImPrivateMessageDO> unreadMessages = List.of( | ||
| 162 | + ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L) | ||
| 163 | + .status(ImMessageStatusEnum.UNREAD.getStatus()).build(), | ||
| 164 | + ImPrivateMessageDO.builder().id(2L).senderId(2L).receiverId(1L) | ||
| 165 | + .status(ImMessageStatusEnum.UNREAD.getStatus()).build() | ||
| 166 | + ); | ||
| 167 | + when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L, | ||
| 168 | + ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages); | ||
| 170 | 169 | ||
| 171 | - // 断言:更新了消息状态 + 发送了 READ 和 RECEIPT 事件 | ||
| 172 | - verify(imPrivateMessageMapper).update(any(ImPrivateMessageDO.class), any()); | ||
| 173 | - verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); | 170 | + // 调用 |
| 171 | + privateMessageService.readPrivateMessages(1L, 2L); | ||
| 172 | + | ||
| 173 | + // 断言:更新了消息状态 + 发送了 READ 和 RECEIPT 事件 | ||
| 174 | + verify(imPrivateMessageMapper).updateByIdsAndStatus(eq(List.of(1L, 2L)), | ||
| 175 | + eq(ImMessageStatusEnum.UNREAD.getStatus()), any(ImPrivateMessageDO.class)); | ||
| 176 | + verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), anyString(), any()); | ||
| 177 | + } | ||
| 174 | } | 178 | } |
| 175 | 179 | ||
| 176 | // ========== 撤回测试 ========== | 180 | // ========== 撤回测试 ========== |
| @@ -208,7 +212,8 @@ public class ImPrivateMessageServiceImplTest { | @@ -208,7 +212,8 @@ public class ImPrivateMessageServiceImplTest { | ||
| 208 | // 准备 | 212 | // 准备 |
| 209 | ImPrivateMessageDO message = ImPrivateMessageDO.builder() | 213 | ImPrivateMessageDO message = ImPrivateMessageDO.builder() |
| 210 | .id(10L).senderId(2L).receiverId(1L) | 214 | .id(10L).senderId(2L).receiverId(1L) |
| 211 | - .status(ImMessageStatusEnum.UNREAD.getStatus()).build(); | 215 | + .status(ImMessageStatusEnum.UNREAD.getStatus()) |
| 216 | + .sendTime(LocalDateTime.now()).build(); | ||
| 212 | when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | 217 | when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); |
| 213 | 218 | ||
| 214 | // 调用并断言 | 219 | // 调用并断言 |
| @@ -222,7 +227,8 @@ public class ImPrivateMessageServiceImplTest { | @@ -222,7 +227,8 @@ public class ImPrivateMessageServiceImplTest { | ||
| 222 | // 准备 | 227 | // 准备 |
| 223 | ImPrivateMessageDO message = ImPrivateMessageDO.builder() | 228 | ImPrivateMessageDO message = ImPrivateMessageDO.builder() |
| 224 | .id(10L).senderId(1L).receiverId(2L) | 229 | .id(10L).senderId(1L).receiverId(2L) |
| 225 | - .status(ImMessageStatusEnum.RECALL.getStatus()).build(); | 230 | + .status(ImMessageStatusEnum.RECALL.getStatus()) |
| 231 | + .sendTime(LocalDateTime.now()).build(); | ||
| 226 | when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | 232 | when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); |
| 227 | 233 | ||
| 228 | // 调用并断言 | 234 | // 调用并断言 |
yudao-module-im/yudao-module-im-biz/src/test/java/cn/iocoder/yudao/module/im/service/message/ImWebSocketEventTest.java
| 1 | package cn.iocoder.yudao.module.im.service.message; | 1 | package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 3 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; | 5 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 4 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 5 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; | 7 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImPrivateMessageMapper; |
| 6 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; | 8 | import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; |
| 9 | +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | ||
| 10 | +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | ||
| 7 | import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; | 11 | import cn.iocoder.yudao.module.im.websocket.ImMessageReadMessage; |
| 8 | import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage; | 12 | import cn.iocoder.yudao.module.im.websocket.ImMessageReceiptMessage; |
| 9 | -import cn.iocoder.yudao.module.im.websocket.ImMessageRecallMessage; | ||
| 10 | import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage; | 13 | import cn.iocoder.yudao.module.im.websocket.ImPrivateMessageSendMessage; |
| 11 | -import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | ||
| 12 | -import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; | ||
| 13 | -import cn.iocoder.yudao.framework.websocket.core.sender.WebSocketMessageSender; | ||
| 14 | import org.junit.jupiter.api.Test; | 14 | import org.junit.jupiter.api.Test; |
| 15 | -import org.junit.jupiter.api.extension.ExtendWith; | ||
| 16 | import org.mockito.ArgumentCaptor; | 15 | import org.mockito.ArgumentCaptor; |
| 17 | import org.mockito.InjectMocks; | 16 | import org.mockito.InjectMocks; |
| 18 | import org.mockito.Mock; | 17 | import org.mockito.Mock; |
| 19 | -import org.mockito.junit.jupiter.MockitoExtension; | ||
| 20 | 18 | ||
| 21 | import java.util.List; | 19 | import java.util.List; |
| 22 | 20 | ||
| 23 | -import static org.junit.jupiter.api.Assertions.*; | 21 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 22 | +import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
| 24 | import static org.mockito.ArgumentMatchers.*; | 23 | import static org.mockito.ArgumentMatchers.*; |
| 25 | import static org.mockito.Mockito.*; | 24 | import static org.mockito.Mockito.*; |
| 26 | 25 | ||
| @@ -31,8 +30,7 @@ import static org.mockito.Mockito.*; | @@ -31,8 +30,7 @@ import static org.mockito.Mockito.*; | ||
| 31 | * | 30 | * |
| 32 | * @author 芋道源码 | 31 | * @author 芋道源码 |
| 33 | */ | 32 | */ |
| 34 | -@ExtendWith(MockitoExtension.class) | ||
| 35 | -public class ImWebSocketEventTest { | 33 | +public class ImWebSocketEventTest extends BaseMockitoUnitTest { |
| 36 | 34 | ||
| 37 | @InjectMocks | 35 | @InjectMocks |
| 38 | private ImPrivateMessageServiceImpl privateMessageService; | 36 | private ImPrivateMessageServiceImpl privateMessageService; |
| @@ -48,92 +46,104 @@ public class ImWebSocketEventTest { | @@ -48,92 +46,104 @@ public class ImWebSocketEventTest { | ||
| 48 | 46 | ||
| 49 | @Test | 47 | @Test |
| 50 | public void testSendMessage_websocketEvent() { | 48 | public void testSendMessage_websocketEvent() { |
| 51 | - // 准备 | ||
| 52 | - ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO(); | ||
| 53 | - reqVO.setClientMessageId("evt-uuid"); | ||
| 54 | - reqVO.setReceiverId(2L); | ||
| 55 | - reqVO.setType(0); | ||
| 56 | - reqVO.setContent("{\"content\":\"事件测试\"}"); | ||
| 57 | - | ||
| 58 | - when(imPrivateMessageMapper.selectBySenderIdAndClientMessageId(1L, "evt-uuid")).thenReturn(null); | ||
| 59 | - // mock insert:通过 doAnswer 模拟 MyBatis-Plus 设置 ID | ||
| 60 | - when(imPrivateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> { | ||
| 61 | - ImPrivateMessageDO msg = invocation.getArgument(0); | ||
| 62 | - msg.setId(99L); | ||
| 63 | - return 1; | ||
| 64 | - }); | ||
| 65 | - | ||
| 66 | - // 调用 | ||
| 67 | - privateMessageService.sendPrivateMessage(1L, reqVO); | ||
| 68 | - | ||
| 69 | - // 捕获 WebSocket 调用 | ||
| 70 | - ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 71 | - ArgumentCaptor<Object> contentCaptor = ArgumentCaptor.forClass(Object.class); | ||
| 72 | - verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), | ||
| 73 | - typeCaptor.capture(), contentCaptor.capture()); | ||
| 74 | - | ||
| 75 | - // 验证消息事件类型 | ||
| 76 | - assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 77 | - assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(1)); | ||
| 78 | - | ||
| 79 | - // 验证 payload 是 DTO 类型 | ||
| 80 | - ImPrivateMessageSendMessage payload = (ImPrivateMessageSendMessage) contentCaptor.getAllValues().get(0); | ||
| 81 | - assertNotNull(payload.getId(), "id 不应为空"); | ||
| 82 | - assertEquals("1", payload.getSenderId()); | ||
| 83 | - assertEquals("2", payload.getReceiverId()); | 49 | + try (var springUtilMockedStatic = mockStatic(cn.hutool.extra.spring.SpringUtil.class)) { |
| 50 | + springUtilMockedStatic.when(() -> cn.hutool.extra.spring.SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class))) | ||
| 51 | + .thenReturn(privateMessageService); | ||
| 52 | + | ||
| 53 | + // 准备 | ||
| 54 | + ImPrivateMessageSendReqVO reqVO = new ImPrivateMessageSendReqVO(); | ||
| 55 | + reqVO.setClientMessageId("evt-uuid"); | ||
| 56 | + reqVO.setReceiverId(2L); | ||
| 57 | + reqVO.setType(0); | ||
| 58 | + reqVO.setContent("{\"content\":\"事件测试\"}"); | ||
| 59 | + | ||
| 60 | + when(imPrivateMessageMapper.selectBySenderIdAndClientMessageId(1L, "evt-uuid")).thenReturn(null); | ||
| 61 | + when(imPrivateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenAnswer(invocation -> { | ||
| 62 | + ImPrivateMessageDO msg = invocation.getArgument(0); | ||
| 63 | + msg.setId(99L); | ||
| 64 | + return 1; | ||
| 65 | + }); | ||
| 66 | + | ||
| 67 | + // 调用 | ||
| 68 | + privateMessageService.sendPrivateMessage(1L, reqVO); | ||
| 69 | + | ||
| 70 | + // 捕获 WebSocket 调用 | ||
| 71 | + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 72 | + ArgumentCaptor<Object> contentCaptor = ArgumentCaptor.forClass(Object.class); | ||
| 73 | + verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), | ||
| 74 | + typeCaptor.capture(), contentCaptor.capture()); | ||
| 75 | + | ||
| 76 | + // 验证消息事件类型 | ||
| 77 | + assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 78 | + assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(1)); | ||
| 79 | + | ||
| 80 | + // 验证 payload 是 DTO 类型 | ||
| 81 | + ImPrivateMessageSendMessage payload = (ImPrivateMessageSendMessage) contentCaptor.getAllValues().get(0); | ||
| 82 | + assertNotNull(payload.getId(), "id 不应为空"); | ||
| 83 | + assertEquals(1L, payload.getSenderId()); | ||
| 84 | + assertEquals(2L, payload.getReceiverId()); | ||
| 85 | + } | ||
| 84 | } | 86 | } |
| 85 | 87 | ||
| 86 | @Test | 88 | @Test |
| 87 | public void testReadMessage_readAndReceiptEvents() { | 89 | public void testReadMessage_readAndReceiptEvents() { |
| 88 | - // 准备:mock 未读消息 | ||
| 89 | - List<ImPrivateMessageDO> unreadMessages = List.of( | ||
| 90 | - ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L) | ||
| 91 | - .status(ImMessageStatusEnum.UNREAD.getStatus()).build() | ||
| 92 | - ); | ||
| 93 | - when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L, | ||
| 94 | - ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages); | ||
| 95 | - | ||
| 96 | - // 调用 | ||
| 97 | - privateMessageService.readPrivateMessages(1L, 2L); | ||
| 98 | - | ||
| 99 | - // 捕获 | ||
| 100 | - ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 101 | - ArgumentCaptor<Long> userCaptor = ArgumentCaptor.forClass(Long.class); | ||
| 102 | - verify(webSocketMessageSender, times(2)).sendObject(anyInt(), userCaptor.capture(), | ||
| 103 | - typeCaptor.capture(), any()); | ||
| 104 | - | ||
| 105 | - // 第一次:发给自己的 READ 事件 | ||
| 106 | - assertEquals(ImMessageReadMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 107 | - assertEquals(1L, userCaptor.getAllValues().get(0)); | ||
| 108 | - // 第二次:发给对方的 RECEIPT 事件 | ||
| 109 | - assertEquals(ImMessageReceiptMessage.TYPE, typeCaptor.getAllValues().get(1)); | ||
| 110 | - assertEquals(2L, userCaptor.getAllValues().get(1)); | 90 | + try (var springUtilMockedStatic = mockStatic(cn.hutool.extra.spring.SpringUtil.class)) { |
| 91 | + springUtilMockedStatic.when(() -> cn.hutool.extra.spring.SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class))) | ||
| 92 | + .thenReturn(privateMessageService); | ||
| 93 | + | ||
| 94 | + // 准备:mock 未读消息 | ||
| 95 | + List<ImPrivateMessageDO> unreadMessages = List.of( | ||
| 96 | + ImPrivateMessageDO.builder().id(1L).senderId(2L).receiverId(1L) | ||
| 97 | + .status(ImMessageStatusEnum.UNREAD.getStatus()).build() | ||
| 98 | + ); | ||
| 99 | + when(imPrivateMessageMapper.selectListBySenderIdAndReceiverIdAndStatus(2L, 1L, | ||
| 100 | + ImMessageStatusEnum.UNREAD.getStatus())).thenReturn(unreadMessages); | ||
| 101 | + | ||
| 102 | + // 调用 | ||
| 103 | + privateMessageService.readPrivateMessages(1L, 2L); | ||
| 104 | + | ||
| 105 | + // 捕获 | ||
| 106 | + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 107 | + ArgumentCaptor<Long> userCaptor = ArgumentCaptor.forClass(Long.class); | ||
| 108 | + verify(webSocketMessageSender, times(2)).sendObject(anyInt(), userCaptor.capture(), | ||
| 109 | + typeCaptor.capture(), any()); | ||
| 110 | + | ||
| 111 | + // 第一次:发给自己的 READ 事件 | ||
| 112 | + assertEquals(ImMessageReadMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 113 | + assertEquals(1L, userCaptor.getAllValues().get(0)); | ||
| 114 | + // 第二次:发给对方的 RECEIPT 事件 | ||
| 115 | + assertEquals(ImMessageReceiptMessage.TYPE, typeCaptor.getAllValues().get(1)); | ||
| 116 | + assertEquals(2L, userCaptor.getAllValues().get(1)); | ||
| 117 | + } | ||
| 111 | } | 118 | } |
| 112 | 119 | ||
| 113 | @Test | 120 | @Test |
| 114 | - public void testRecallMessage_recallEvent() { | ||
| 115 | - // 准备 | ||
| 116 | - ImPrivateMessageDO message = ImPrivateMessageDO.builder() | ||
| 117 | - .id(10L).senderId(1L).receiverId(2L).status(0).build(); | ||
| 118 | - when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | ||
| 119 | - when(imPrivateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); | ||
| 120 | - | ||
| 121 | - // 调用 | ||
| 122 | - privateMessageService.recallPrivateMessage(1L, 10L); | ||
| 123 | - | ||
| 124 | - // 捕获 | ||
| 125 | - ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 126 | - ArgumentCaptor<Object> contentCaptor = ArgumentCaptor.forClass(Object.class); | ||
| 127 | - verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), | ||
| 128 | - typeCaptor.capture(), contentCaptor.capture()); | ||
| 129 | - | ||
| 130 | - // 验证 RECALL 事件类型 | ||
| 131 | - assertEquals(ImMessageRecallMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 132 | - | ||
| 133 | - // 验证 payload | ||
| 134 | - ImMessageRecallMessage payload = (ImMessageRecallMessage) contentCaptor.getAllValues().get(0); | ||
| 135 | - assertEquals("10", payload.getMessageId()); | ||
| 136 | - assertEquals("private", payload.getMessageScene()); | 121 | + public void testRecallMessage_tipTextEvent() { |
| 122 | + try (var springUtilMockedStatic = mockStatic(cn.hutool.extra.spring.SpringUtil.class)) { | ||
| 123 | + springUtilMockedStatic.when(() -> cn.hutool.extra.spring.SpringUtil.getBean(eq(ImPrivateMessageServiceImpl.class))) | ||
| 124 | + .thenReturn(privateMessageService); | ||
| 125 | + | ||
| 126 | + // 准备 | ||
| 127 | + ImPrivateMessageDO message = ImPrivateMessageDO.builder() | ||
| 128 | + .id(10L).senderId(1L).receiverId(2L).status(0) | ||
| 129 | + .sendTime(java.time.LocalDateTime.now()).build(); | ||
| 130 | + when(imPrivateMessageMapper.selectById(10L)).thenReturn(message); | ||
| 131 | + when(imPrivateMessageMapper.updateById(any(ImPrivateMessageDO.class))).thenReturn(1); | ||
| 132 | + when(imPrivateMessageMapper.insert(any(ImPrivateMessageDO.class))).thenReturn(1); | ||
| 133 | + | ||
| 134 | + // 调用 | ||
| 135 | + privateMessageService.recallPrivateMessage(1L, 10L); | ||
| 136 | + | ||
| 137 | + // 捕获 | ||
| 138 | + ArgumentCaptor<String> typeCaptor = ArgumentCaptor.forClass(String.class); | ||
| 139 | + ArgumentCaptor<Object> contentCaptor = ArgumentCaptor.forClass(Object.class); | ||
| 140 | + verify(webSocketMessageSender, times(2)).sendObject(anyInt(), anyLong(), | ||
| 141 | + typeCaptor.capture(), contentCaptor.capture()); | ||
| 142 | + | ||
| 143 | + // 验证:推送的是 TIP_TEXT 消息(通过 sendPrivateMessageEvent) | ||
| 144 | + assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(0)); | ||
| 145 | + assertEquals(ImPrivateMessageSendMessage.TYPE, typeCaptor.getAllValues().get(1)); | ||
| 146 | + } | ||
| 137 | } | 147 | } |
| 138 | 148 | ||
| 139 | } | 149 | } |
yudao-module-im/yudao-module-im-biz/src/test/resources/application-unit-test.yaml
| 1 | spring: | 1 | spring: |
| 2 | main: | 2 | main: |
| 3 | - lazy-initialization: true | ||
| 4 | - banner-mode: off | 3 | + lazy-initialization: true # 开启懒加载,加快速度 |
| 4 | + banner-mode: off # 单元测试,禁用 Banner | ||
| 5 | 5 | ||
| 6 | --- #################### 数据库相关配置 #################### | 6 | --- #################### 数据库相关配置 #################### |
| 7 | 7 | ||
| 8 | spring: | 8 | spring: |
| 9 | + # 数据源配置项 | ||
| 9 | datasource: | 10 | datasource: |
| 10 | name: ruoyi-vue-pro | 11 | name: ruoyi-vue-pro |
| 11 | - url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; | 12 | + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 |
| 12 | driver-class-name: org.h2.Driver | 13 | driver-class-name: org.h2.Driver |
| 13 | username: sa | 14 | username: sa |
| 14 | password: | 15 | password: |
| 15 | druid: | 16 | druid: |
| 16 | - async-init: true | ||
| 17 | - initial-size: 1 | 17 | + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 |
| 18 | + initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||
| 18 | sql: | 19 | sql: |
| 19 | init: | 20 | init: |
| 20 | schema-locations: classpath:/sql/create_tables.sql | 21 | schema-locations: classpath:/sql/create_tables.sql |
| 21 | encoding: UTF-8 | 22 | encoding: UTF-8 |
| 22 | 23 | ||
| 24 | + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||
| 23 | data: | 25 | data: |
| 24 | redis: | 26 | redis: |
| 25 | - host: 127.0.0.1 | ||
| 26 | - port: 16379 | ||
| 27 | - database: 0 | 27 | + host: 127.0.0.1 # 地址 |
| 28 | + port: 16379 # 端口(单元测试,使用 16379 端口) | ||
| 29 | + database: 0 # 数据库索引 | ||
| 28 | 30 | ||
| 29 | -mybatis-plus: | ||
| 30 | - lazy-initialization: true | ||
| 31 | - type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject | 31 | +mybatis: |
| 32 | + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 | ||
| 33 | + | ||
| 34 | +--- #################### 定时任务相关配置 #################### | ||
| 35 | + | ||
| 36 | +--- #################### 配置中心相关配置 #################### | ||
| 37 | + | ||
| 38 | +--- #################### 服务保障相关配置 #################### | ||
| 39 | + | ||
| 40 | +# Lock4j 配置项(单元测试,禁用 Lock4j) | ||
| 41 | + | ||
| 42 | +--- #################### 监控相关配置 #################### | ||
| 32 | 43 | ||
| 33 | --- #################### 芋道相关配置 #################### | 44 | --- #################### 芋道相关配置 #################### |
| 34 | 45 | ||
| 46 | +# 芋道配置项,设置当前项目所有自定义的配置 | ||
| 35 | yudao: | 47 | yudao: |
| 36 | info: | 48 | info: |
| 37 | - base-package: cn.iocoder.yudao | 49 | + base-package: cn.iocoder.yudao.module |