Commit 71d2f7411091d709c5a777528da6ccef2fa56363

Authored by YunaiV
1 parent 6aeeeac9

完成 SysSmsServiceTest 方法的单元测试

src/main/java/cn/iocoder/dashboard/modules/system/service/sms/impl/SysSmsServiceImpl.java
... ... @@ -18,6 +18,7 @@ import cn.iocoder.dashboard.modules.system.service.sms.SysSmsLogService;
18 18 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsService;
19 19 import cn.iocoder.dashboard.modules.system.service.sms.SysSmsTemplateService;
20 20 import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
  21 +import com.google.common.annotations.VisibleForTesting;
21 22 import lombok.extern.slf4j.Slf4j;
22 23 import org.springframework.stereotype.Service;
23 24 import org.springframework.util.Assert;
... ... @@ -77,6 +78,8 @@ public class SysSmsServiceImpl implements SysSmsService {
77 78 SysSmsTemplateDO template = this.checkSmsTemplateValid(templateCode);
78 79 // 校验手机号码是否存在
79 80 mobile = this.checkMobile(mobile);
  81 + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志
  82 + List<KeyValue<String, Object>> newTemplateParams = this.buildTemplateParams(template, templateParams);
80 83  
81 84 // 创建发送日志
82 85 Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()); // 如果模板被禁用,则不发送短信,只记录日志
... ... @@ -85,7 +88,6 @@ public class SysSmsServiceImpl implements SysSmsService {
85 88  
86 89 // 发送 MQ 消息,异步执行发送短信
87 90 if (isSend) {
88   - List<KeyValue<String, Object>> newTemplateParams = this.buildTemplateParams(template, templateParams);
89 91 smsProducer.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(), template.getApiTemplateId(), newTemplateParams);
90 92 }
91 93 return sendLogId;
... ... @@ -97,7 +99,8 @@ public class SysSmsServiceImpl implements SysSmsService {
97 99 throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!");
98 100 }
99 101  
100   - private SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
  102 + @VisibleForTesting
  103 + public SysSmsTemplateDO checkSmsTemplateValid(String templateCode) {
101 104 // 获得短信模板。考虑到效率,从缓存中获取
102 105 SysSmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);
103 106 // 短信模板不存在
... ... @@ -116,7 +119,8 @@ public class SysSmsServiceImpl implements SysSmsService {
116 119 * @param templateParams 原始参数
117 120 * @return 处理后的参数
118 121 */
119   - private List<KeyValue<String, Object>> buildTemplateParams(SysSmsTemplateDO template, Map<String, Object> templateParams) {
  122 + @VisibleForTesting
  123 + public List<KeyValue<String, Object>> buildTemplateParams(SysSmsTemplateDO template, Map<String, Object> templateParams) {
120 124 return template.getParams().stream().map(key -> {
121 125 Object value = templateParams.get(key);
122 126 if (value == null) {
... ... @@ -126,7 +130,8 @@ public class SysSmsServiceImpl implements SysSmsService {
126 130 }).collect(Collectors.toList());
127 131 }
128 132  
129   - private String checkMobile(String mobile) {
  133 + @VisibleForTesting
  134 + public String checkMobile(String mobile) {
130 135 if (StrUtil.isEmpty(mobile)) {
131 136 throw exception(SMS_SEND_MOBILE_NOT_EXISTS);
132 137 }
... ...
src/test/java/cn/iocoder/dashboard/BaseMockitoUnitTest.java 0 → 100644
  1 +package cn.iocoder.dashboard;
  2 +
  3 +import org.junit.jupiter.api.extension.ExtendWith;
  4 +import org.mockito.junit.jupiter.MockitoExtension;
  5 +
  6 +/**
  7 + * 纯 Mockito 的单元测试
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@ExtendWith(MockitoExtension.class)
  12 +public class BaseMockitoUnitTest {
  13 +}
... ...
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsServiceTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.sms;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.iocoder.dashboard.BaseMockitoUnitTest;
  5 +import cn.iocoder.dashboard.common.core.KeyValue;
  6 +import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
  7 +import cn.iocoder.dashboard.common.enums.UserTypeEnum;
  8 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClient;
  9 +import cn.iocoder.dashboard.framework.sms.core.client.SmsClientFactory;
  10 +import cn.iocoder.dashboard.framework.sms.core.client.SmsCommonResult;
  11 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsReceiveRespDTO;
  12 +import cn.iocoder.dashboard.framework.sms.core.client.dto.SmsSendRespDTO;
  13 +import cn.iocoder.dashboard.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
  14 +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  15 +import cn.iocoder.dashboard.modules.system.mq.producer.sms.SysSmsProducer;
  16 +import cn.iocoder.dashboard.modules.system.service.sms.impl.SysSmsServiceImpl;
  17 +import org.assertj.core.util.Lists;
  18 +import org.junit.jupiter.api.Test;
  19 +import org.mockito.InjectMocks;
  20 +import org.mockito.Mock;
  21 +
  22 +import java.util.HashMap;
  23 +import java.util.List;
  24 +import java.util.Map;
  25 +
  26 +import static cn.hutool.core.util.RandomUtil.randomEle;
  27 +import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
  28 +import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
  29 +import static cn.iocoder.dashboard.util.RandomUtils.*;
  30 +import static org.junit.jupiter.api.Assertions.assertEquals;
  31 +import static org.mockito.ArgumentMatchers.eq;
  32 +import static org.mockito.Mockito.*;
  33 +
  34 +/**
  35 + * {@link SysSmsServiceImpl} 的单元测试类
  36 + *
  37 + * @author 芋道源码
  38 + */
  39 +public class SysSmsServiceTest extends BaseMockitoUnitTest {
  40 +
  41 + @InjectMocks
  42 + private SysSmsServiceImpl smsService;
  43 +
  44 + @Mock
  45 + private SysSmsTemplateService smsTemplateService;
  46 + @Mock
  47 + private SysSmsLogService smsLogService;
  48 + @Mock
  49 + private SysSmsProducer smsProducer;
  50 + @Mock
  51 + private SmsClientFactory smsClientFactory;
  52 +
  53 + /**
  54 + * 发送成功,当短信模板开启时
  55 + */
  56 + @Test
  57 + public void testSendSingleSms_successWhenSmsTemplateEnable() {
  58 + // 准备参数
  59 + String mobile = randomString();
  60 + Long userId = randomLongId();
  61 + Integer userType = randomEle(UserTypeEnum.values()).getValue();
  62 + String templateCode = randomString();
  63 + Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
  64 + .put("op", "login").build();
  65 + // mock SmsTemplateService 的方法
  66 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class, o -> {
  67 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  68 + o.setContent("验证码为{code}, 操作为{op}");
  69 + o.setParams(Lists.newArrayList("code", "op"));
  70 + });
  71 + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
  72 + String content = randomString();
  73 + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))
  74 + .thenReturn(content);
  75 + // mock SmsLogService 的方法
  76 + Long smsLogId = randomLongId();
  77 + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.TRUE), eq(template),
  78 + eq(content), eq(templateParams))).thenReturn(smsLogId);
  79 +
  80 + // 调用
  81 + Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  82 + // 断言
  83 + assertEquals(smsLogId, resultSmsLogId);
  84 + // 断言调用
  85 + verify(smsProducer, times(1)).sendSmsSendMessage(eq(smsLogId), eq(mobile),
  86 + eq(template.getChannelId()), eq(template.getApiTemplateId()),
  87 + eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login"))));
  88 + }
  89 +
  90 + /**
  91 + * 发送成功,当短信模板关闭时
  92 + */
  93 + @Test
  94 + public void testSendSingleSms_successWhenSmsTemplateDisable() {
  95 + // 准备参数
  96 + String mobile = randomString();
  97 + Long userId = randomLongId();
  98 + Integer userType = randomEle(UserTypeEnum.values()).getValue();
  99 + String templateCode = randomString();
  100 + Map<String, Object> templateParams = MapUtil.<String, Object>builder().put("code", "1234")
  101 + .put("op", "login").build();
  102 + // mock SmsTemplateService 的方法
  103 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class, o -> {
  104 + o.setStatus(CommonStatusEnum.DISABLE.getStatus());
  105 + o.setContent("验证码为{code}, 操作为{op}");
  106 + o.setParams(Lists.newArrayList("code", "op"));
  107 + });
  108 + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);
  109 + String content = randomString();
  110 + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))
  111 + .thenReturn(content);
  112 + // mock SmsLogService 的方法
  113 + Long smsLogId = randomLongId();
  114 + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.FALSE), eq(template),
  115 + eq(content), eq(templateParams))).thenReturn(smsLogId);
  116 +
  117 + // 调用
  118 + Long resultSmsLogId = smsService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);
  119 + // 断言
  120 + assertEquals(smsLogId, resultSmsLogId);
  121 + // 断言调用
  122 + verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), anyString(),
  123 + anyLong(), any(), anyList());
  124 + }
  125 +
  126 + @Test
  127 + public void testCheckSmsTemplateValid_notExists() {
  128 + // 准备参数
  129 + String templateCode = randomString();
  130 + // mock 方法
  131 +
  132 + // 调用,并断言异常
  133 + assertServiceException(() -> smsService.checkSmsTemplateValid(templateCode),
  134 + SMS_TEMPLATE_NOT_EXISTS);
  135 + }
  136 +
  137 + @Test
  138 + public void testBuildTemplateParams_paramMiss() {
  139 + // 准备参数
  140 + SysSmsTemplateDO template = randomPojo(SysSmsTemplateDO.class,
  141 + o -> o.setParams(Lists.newArrayList("code")));
  142 + Map<String, Object> templateParams = new HashMap<>();
  143 + // mock 方法
  144 +
  145 + // 调用,并断言异常
  146 + assertServiceException(() -> smsService.buildTemplateParams(template, templateParams),
  147 + SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code");
  148 + }
  149 +
  150 + @Test
  151 + public void testCheckMobile_notExists() {
  152 + // 准备参数
  153 + // mock 方法
  154 +
  155 + // 调用,并断言异常
  156 + assertServiceException(() -> smsService.checkMobile(null),
  157 + SMS_SEND_MOBILE_NOT_EXISTS);
  158 + }
  159 +
  160 + @Test
  161 + @SuppressWarnings("unchecked")
  162 + public void testDoSendSms() {
  163 + // 准备参数
  164 + SysSmsSendMessage message = randomPojo(SysSmsSendMessage.class);
  165 + // mock SmsClientFactory 的方法
  166 + SmsClient smsClient = spy(SmsClient.class);
  167 + when(smsClientFactory.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient);
  168 + // mock SmsClient 的方法
  169 + SmsCommonResult<SmsSendRespDTO> sendResult = randomPojo(SmsCommonResult.class, SmsSendRespDTO.class);
  170 + when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()),
  171 + eq(message.getTemplateParams()))).thenReturn(sendResult);
  172 +
  173 + // 调用
  174 + smsService.doSendSms(message);
  175 + // 断言
  176 + verify(smsLogService, times(1)).updateSmsSendResult(eq(message.getLogId()),
  177 + eq(sendResult.getCode()), eq(sendResult.getMsg()), eq(sendResult.getApiCode()),
  178 + eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getData().getSerialNo()));
  179 + }
  180 +
  181 + @Test
  182 + public void testReceiveSmsStatus() throws Throwable {
  183 + // 准备参数
  184 + String channelCode = randomString();
  185 + String text = randomString();
  186 + // mock SmsClientFactory 的方法
  187 + SmsClient smsClient = spy(SmsClient.class);
  188 + when(smsClientFactory.getSmsClient(eq(channelCode))).thenReturn(smsClient);
  189 + // mock SmsClient 的方法
  190 + List<SmsReceiveRespDTO> receiveResults = randomPojoList(SmsReceiveRespDTO.class);
  191 +
  192 + // 调用
  193 + smsService.receiveSmsStatus(channelCode, text);
  194 + // 断言
  195 + receiveResults.forEach(result -> {
  196 + smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()),
  197 + eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorCode()));
  198 + });
  199 + }
  200 +
  201 +}
... ...
src/test/java/cn/iocoder/dashboard/util/AopTargetUtils.java
... ... @@ -5,43 +5,42 @@ import org.springframework.aop.framework.AdvisedSupport;
5 5 import org.springframework.aop.framework.AopProxy;
6 6 import org.springframework.aop.support.AopUtils;
7 7  
8   -import java.lang.reflect.Field;
9   -
10 8 /**
11   - * http://www.bubuko.com/infodetail-3471885.html
  9 + * Spring AOP 工具类
  10 + *
  11 + * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
12 12 */
13 13 public class AopTargetUtils {
14 14  
15 15 /**
16   - * 获取 目标对象
  16 + * 获取代理的目标对象
17 17 *
18 18 * @param proxy 代理对象
19   - * @return
20   - * @throws Exception
  19 + * @return 目标对象
21 20 */
22 21 public static Object getTarget(Object proxy) throws Exception {
  22 + // 不是代理对象
23 23 if (!AopUtils.isAopProxy(proxy)) {
24   - return proxy; //不是代理对象
  24 + return proxy;
25 25 }
  26 + // Jdk 代理
26 27 if (AopUtils.isJdkDynamicProxy(proxy)) {
27 28 return getJdkDynamicProxyTargetObject(proxy);
28   - } else { //cglib
29   - return getCglibProxyTargetObject(proxy);
30 29 }
  30 + // Cglib 代理
  31 + return getCglibProxyTargetObject(proxy);
31 32 }
32 33  
33 34 private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
34 35 Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
35 36 AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
36   - Object target = advisedSupport.getTargetSource().getTarget();
37   - return target;
  37 + return advisedSupport.getTargetSource().getTarget();
38 38 }
39 39  
40 40 private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
41 41 AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
42 42 AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
43   - Object target = advisedSupport.getTargetSource().getTarget();
44   - return target;
  43 + return advisedSupport.getTargetSource().getTarget();
45 44 }
46 45  
47   -}
48 46 \ No newline at end of file
  47 +}
... ...
src/test/java/cn/iocoder/dashboard/util/RandomUtils.java
... ... @@ -8,9 +8,7 @@ import uk.co.jemos.podam.api.PodamFactory;
8 8 import uk.co.jemos.podam.api.PodamFactoryImpl;
9 9  
10 10 import java.lang.reflect.Type;
11   -import java.util.Arrays;
12   -import java.util.Date;
13   -import java.util.Set;
  11 +import java.util.*;
14 12 import java.util.function.Consumer;
15 13 import java.util.stream.Collectors;
16 14 import java.util.stream.Stream;
... ... @@ -98,4 +96,11 @@ public class RandomUtils {
98 96 return pojo;
99 97 }
100 98  
  99 + @SafeVarargs
  100 + public static <T> List<T> randomPojoList(Class<T> clazz, Consumer<T>... consumers) {
  101 + int size = RandomUtil.randomInt(0, RANDOM_COLLECTION_LENGTH);
  102 + return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))
  103 + .collect(Collectors.toList());
  104 + }
  105 +
101 106 }
... ...