refactor(model): 重构模型类包结构并优化序列化处理

重构模型类包结构,将模型类按功能模块划分到不同的子包中。优化序列化处理,为VO类添加serialVersionUID并实现Serializable接口。移除部分冗余的serialVersionUID字段,简化模型类代码。同时修复UITools中空值处理的问题,并更新pom版本至0.0.100-SNAPSHOT。

- 将模型类按功能模块划分到ds子包中
- 为VO类添加序列化支持
- 移除冗余的serialVersionUID字段
- 修复UITools空值处理问题
- 更新项目版本号
This commit is contained in:
2025-10-09 18:27:48 +08:00
parent 51b8c16798
commit c4eec0a9dd
457 changed files with 8426 additions and 3669 deletions

View File

@@ -0,0 +1,208 @@
package com.ecep.contract.ds.contract.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.ds.contract.repository.ContractRepository;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.model.ContractType;
import com.ecep.contract.ds.project.model.Project;
import com.ecep.contract.vo.ContractVo;
import com.ecep.contract.service.BaseServiceTest;
import com.ecep.contract.service.VoableService;
/**
* ContractService的集成测试类用于验证ContractService的实体-VO转换和缓存逻辑
*/
@SpringBootTest
class ContractServiceIntegrationTest extends BaseServiceTest<Contract, ContractVo> {
@MockBean
private ContractRepository contractRepository;
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@InjectMocks
private ContractService contractService;
@Override
protected IEntityService<Contract> getEntityService() {
return contractService;
}
@Override
protected QueryService<ContractVo> getQueryService() {
return contractService;
}
@Override
protected VoableService<Contract, ContractVo> getVoableService() {
return contractService;
}
@Override
protected Contract createMockEntity() {
Contract contract = new Contract();
contract.setId(getMockId());
contract.setName("测试合同");
contract.setCode("CONTRACT-001");
// 创建并设置项目对象
Project project = new Project();
project.setId(1);
contract.setProject(project);
// 创建并设置合同类型对象
ContractType contractType = new ContractType();
contractType.setId(1);
contract.setType(contractType);
contract.setAmount(10000.0);
contract.setState("B"); // 根据Contract类中的注释B是最常见的状态
return contract;
}
@Override
protected ContractVo createMockVo() {
Contract entity = createMockEntity();
return entity.toVo();
}
@Override
protected String getRedisKeyPrefix() {
return "contract:";
}
@Override
protected Integer getMockId() {
return 1;
}
@Override
protected String getSearchText() {
return "测试";
}
/**
* 测试ContractService特有的findById方法验证实体-VO转换和缓存逻辑
*/
@Test
void testContractFindById() {
Integer id = getMockId();
Contract mockEntity = createMockEntity();
ContractVo expectedVo = createMockVo();
// 模拟Redis缓存未命中和服务方法调用
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(contractRepository.findById(id)).thenReturn(java.util.Optional.of(mockEntity));
// 执行方法
ContractVo result = contractService.findById(id);
// 验证结果
assertNotNull(result);
assertEquals(expectedVo.getId(), result.getId());
assertEquals(expectedVo.getName(), result.getName());
assertEquals(expectedVo.getCode(), result.getCode());
// 验证方法调用
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(contractRepository, times(1)).findById(id);
verify(redisTemplate, times(1)).opsForValue().set(eq(getRedisKeyPrefix() + id), eq(result), anyLong(), any());
}
/**
* 测试ContractService特有的updateByVo方法验证特定字段的更新逻辑
*/
@Test
void testContractUpdateByVo() {
Integer id = getMockId();
Contract mockEntity = createMockEntity();
ContractVo mockVo = createMockVo();
mockVo.setName("更新后的合同名称");
mockVo.setAmount(20000.0);
// 模拟getById方法调用
when(contractRepository.findById(id)).thenReturn(java.util.Optional.of(mockEntity));
when(contractRepository.save(any(Contract.class))).thenAnswer(i -> i.getArguments()[0]);
// 执行方法
contractService.updateByVo(mockEntity, mockVo);
// 验证结果 - 直接检查传入的实体对象是否被正确更新
assertNotNull(mockEntity);
assertEquals(mockVo.getName(), mockEntity.getName());
assertEquals(mockVo.getAmount(), mockEntity.getAmount());
// code字段应该没有被修改因为我们使用的是传入的mockEntity的code
assertEquals("CONTRACT-001", mockEntity.getCode()); // 未修改的字段保持不变
// 验证方法调用
verify(contractRepository, times(1)).findById(id);
verify(contractRepository, times(1)).save(mockEntity);
verify(redisTemplate, times(1)).delete(getRedisKeyPrefix() + id);
}
/**
* 测试ContractService的缓存刷新机制
*/
@Test
void testContractCacheRefresh() {
Integer id = getMockId();
Contract mockEntity1 = createMockEntity();
Contract mockEntity2 = createMockEntity();
mockEntity2.setName("刷新后的合同名称");
// 第一次调用:缓存未命中
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(contractRepository.findById(id)).thenReturn(java.util.Optional.of(mockEntity1));
ContractVo result1 = contractService.findById(id);
assertEquals(mockEntity1.getName(), result1.getName());
// 模拟实体更新
reset(redisTemplate);
when(contractRepository.findById(id)).thenReturn(java.util.Optional.of(mockEntity2));
when(contractService.findById(id)).thenCallRealMethod();
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
// 第二次调用:应该获取到更新后的数据
ContractVo result2 = contractService.findById(id);
assertEquals(mockEntity2.getName(), result2.getName());
// 验证缓存被更新
verify(redisTemplate, times(1)).opsForValue().set(eq(getRedisKeyPrefix() + id), eq(result2), anyLong(), any());
}
/**
* 测试ContractService的批量操作后缓存清理
*/
@Test
void testContractBatchOperationCacheEviction() {
// 模拟批量更新操作
Contract mockEntity = createMockEntity();
when(contractRepository.save(any(Contract.class))).thenReturn(mockEntity);
// 执行保存操作
contractService.save(mockEntity);
// 验证缓存被清理
verify(redisTemplate, times(1)).delete(anyString());
}
}

View File

@@ -10,7 +10,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -26,7 +25,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import com.ecep.contract.ds.contract.repository.ContractRepository;
import com.ecep.contract.model.Contract;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.vo.ContractVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -3,14 +3,16 @@ package com.ecep.contract.ds.sale.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
import java.util.ArrayList;
import java.time.LocalDate;
import java.util.Arrays;
@@ -22,62 +24,113 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.ds.contract.repository.SalesOrderRepository;
import com.ecep.contract.ds.contract.service.SaleOrdersService;
import com.ecep.contract.model.SalesOrder;
import com.ecep.contract.ds.customer.model.SalesOrder;
import com.ecep.contract.service.BaseServiceTest;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.vo.SalesOrderVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* SaleOrdersService的测试类用于验证实体-VO转换机制和缓存策略
* SaleOrdersService的集成测试类,用于验证SaleOrdersService的实体-VO转换和缓存逻辑
*/
public class SaleOrdersServiceIntegrationTest {
@SpringBootTest
public class SaleOrdersServiceIntegrationTest extends BaseServiceTest<SalesOrder, SalesOrderVo> {
@Mock
private SalesOrderRepository repository;
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@InjectMocks
private SaleOrdersService service;
private ObjectMapper objectMapper;
private SalesOrder mockSalesOrder;
private SalesOrderVo expectedVo;
@Override
protected IEntityService<SalesOrder> getEntityService() {
return service;
}
@Override
protected QueryService<SalesOrderVo> getQueryService() {
return service;
}
@Override
protected VoableService<SalesOrder, SalesOrderVo> getVoableService() {
return service;
}
@Override
protected SalesOrder createMockEntity() {
SalesOrder salesOrder = new SalesOrder();
salesOrder.setId(getMockId());
salesOrder.setCode("SO-TEST001");
salesOrder.setMakerDate(LocalDate.now());
return salesOrder;
}
@Override
protected SalesOrderVo createMockVo() {
SalesOrder entity = createMockEntity();
SalesOrderVo vo = new SalesOrderVo();
vo.setId(entity.getId());
vo.setCode(entity.getCode());
// 复制其他需要的属性
return vo;
}
@Override
protected String getRedisKeyPrefix() {
return "sale_order:";
}
@Override
protected Integer getMockId() {
return 1;
}
@Override
protected String getSearchText() {
return "SO-TEST";
}
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
objectMapper = new ObjectMapper();
// 初始化测试数据
mockSalesOrder = new SalesOrder();
mockSalesOrder.setId(1); // 从Long改为Integer
mockSalesOrder.setCode("SO-TEST001");
mockSalesOrder.setMakerDate(LocalDate.now()); // 使用makerDate而不是createDate
expectedVo = new SalesOrderVo();
expectedVo.setId(1); // 从Long改为Integer
expectedVo.setCode("SO-TEST001");
// 删除不存在的属性设置
}
/**
* 测试findById方法验证实体-VO转换
* 测试findById方法验证实体-VO转换和缓存逻辑
*/
@Test
public void testFindById() {
// 配置mock行为
when(repository.findById(1)).thenReturn(Optional.of(mockSalesOrder));
Integer id = getMockId();
SalesOrder mockEntity = createMockEntity();
SalesOrderVo expectedVo = createMockVo();
// 模拟Redis缓存未命中和服务方法调用
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(repository.findById(id)).thenReturn(Optional.of(mockEntity));
// 执行方法
SalesOrderVo result = service.findById(1);
SalesOrderVo result = service.findById(id);
// 验证结果
assertNotNull(result);
@@ -85,7 +138,9 @@ public class SaleOrdersServiceIntegrationTest {
assertEquals(expectedVo.getCode(), result.getCode());
// 验证方法调用
verify(repository, times(1)).findById(1);
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(repository, times(1)).findById(id);
verify(redisTemplate, times(1)).opsForValue().set(eq(getRedisKeyPrefix() + id), eq(result), anyLong(), any());
}
/**
@@ -93,67 +148,71 @@ public class SaleOrdersServiceIntegrationTest {
*/
@Test
public void testFindById_EntityNotFound() {
Integer id = 999;
// 配置mock行为
when(repository.findById(999)).thenReturn(Optional.empty());
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(repository.findById(id)).thenReturn(Optional.empty());
// 执行方法
SalesOrderVo result = service.findById(999);
SalesOrderVo result = service.findById(id);
// 验证结果
assertNull(result);
// 验证方法调用
verify(repository, times(1)).findById(999);
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(repository, times(1)).findById(id);
}
/**
* 测试save方法验证保存逻辑
* 测试save方法验证保存逻辑和缓存清理
*/
@Test
public void testSave() {
SalesOrder mockEntity = createMockEntity();
// 配置mock行为
when(repository.save(any(SalesOrder.class))).thenReturn(mockSalesOrder);
when(repository.save(any(SalesOrder.class))).thenReturn(mockEntity);
// 执行方法
SalesOrder result = service.save(mockSalesOrder);
SalesOrder result = service.save(mockEntity);
// 验证结果
assertNotNull(result);
assertEquals(mockSalesOrder.getId(), result.getId());
assertEquals(mockEntity.getId(), result.getId());
// 验证方法调用
verify(repository, times(1)).save(mockSalesOrder);
verify(repository, times(1)).save(mockEntity);
// 验证缓存清理
verify(redisTemplate, times(1)).delete(anyString());
}
/**
* 测试delete方法验证删除逻辑
* 测试delete方法验证删除逻辑和缓存清理
*/
@Test
public void testDelete() {
SalesOrder mockEntity = createMockEntity();
// 执行方法
service.delete(mockSalesOrder);
service.delete(mockEntity);
// 验证方法调用
verify(repository, times(1)).delete(mockSalesOrder);
verify(repository, times(1)).delete(mockEntity);
// 验证缓存清理
verify(redisTemplate, times(1)).delete(getRedisKeyPrefix() + mockEntity.getId());
}
/**
* 测试findAll方法验证分页功能
* 测试findAll方法验证分页功能和VO转换
*/
@Test
public void testFindAll() {
// 准备测试数据
List<SalesOrder> salesOrders = Arrays.asList(mockSalesOrder);
List<SalesOrder> salesOrders = Arrays.asList(createMockEntity());
Pageable pageable = PageRequest.of(0, 10);
Page<SalesOrder> pageResult = new PageImpl<>(salesOrders, pageable, salesOrders.size());
// 创建一个简单的SalesOrderVo用于测试
List<SalesOrderVo> voList = new ArrayList<>();
SalesOrderVo vo = new SalesOrderVo();
vo.setId(1);
vo.setCode("SO-TEST001");
voList.add(vo);
Page<SalesOrderVo> voPageResult = new PageImpl<>(voList, pageable, voList.size());
// 配置repository.findAll的mock行为
doReturn(pageResult).when(repository).findAll(any(Specification.class), any(Pageable.class));
@@ -171,14 +230,10 @@ public class SaleOrdersServiceIntegrationTest {
// 验证方法调用
verify(repository, times(1)).findAll(any(Specification.class), any(Pageable.class));
} catch (NullPointerException e) {
// 由于通过repository mock仍然无法解决问题我们采用更直接的方法
// 直接返回我们准备好的VO分页结果
Page<SalesOrderVo> result = voPageResult;
// 验证结果
assertNotNull(result);
assertEquals(1, result.getTotalElements());
} catch (Exception e) {
// 如果发生异常,记录并断言失败
e.printStackTrace();
assertNull("Method should not throw exception: " + e.getMessage());
}
}
@@ -188,7 +243,7 @@ public class SaleOrdersServiceIntegrationTest {
@Test
public void testFindAll_WithParams() throws Exception {
// 准备测试数据
List<SalesOrder> salesOrders = Arrays.asList(mockSalesOrder);
List<SalesOrder> salesOrders = Arrays.asList(createMockEntity());
Pageable pageable = PageRequest.of(0, 10);
Page<SalesOrder> pageResult = new PageImpl<>(salesOrders, pageable, salesOrders.size());
@@ -199,8 +254,8 @@ public class SaleOrdersServiceIntegrationTest {
// 配置mock行为
when(repository.findAll(any(Specification.class), eq(pageable))).thenReturn(pageResult);
// 执行方法 - 确保明确调用QueryService接口的方法
Page<SalesOrderVo> result = ((com.ecep.contract.QueryService<SalesOrderVo>) service).findAll(paramsNode, pageable);
// 执行方法
Page<SalesOrderVo> result = service.findAll(paramsNode, pageable);
// 验证结果
assertNotNull(result);
@@ -209,4 +264,57 @@ public class SaleOrdersServiceIntegrationTest {
// 验证方法调用
verify(repository, times(1)).findAll(any(Specification.class), eq(pageable));
}
/**
* 测试SaleOrdersService的缓存刷新机制
*/
@Test
void testSaleOrdersCacheRefresh() {
Integer id = getMockId();
SalesOrder mockEntity1 = createMockEntity();
SalesOrder mockEntity2 = createMockEntity();
mockEntity2.setCode("SO-TEST002");
// 第一次调用:缓存未命中
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(repository.findById(id)).thenReturn(Optional.of(mockEntity1));
SalesOrderVo result1 = service.findById(id);
assertEquals(mockEntity1.getCode(), result1.getCode());
// 模拟实体更新
reset(redisTemplate);
when(repository.findById(id)).thenReturn(Optional.of(mockEntity2));
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
// 第二次调用:应该获取到更新后的数据
SalesOrderVo result2 = service.findById(id);
assertEquals(mockEntity2.getCode(), result2.getCode());
// 验证缓存被更新
verify(redisTemplate, times(1)).opsForValue().set(eq(getRedisKeyPrefix() + id), eq(result2), anyLong(), any());
}
/**
* 测试SaleOrdersService的缓存命中情况
*/
@Test
public void testFindById_CacheHit() {
Integer id = getMockId();
SalesOrderVo expectedVo = createMockVo();
// 模拟Redis缓存命中
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(expectedVo);
// 执行方法
SalesOrderVo result = service.findById(id);
// 验证结果
assertNotNull(result);
assertEquals(expectedVo, result);
// 验证方法调用 - 不应调用repository.findById
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(repository, times(0)).findById(anyInt());
}
}

View File

@@ -27,7 +27,7 @@ import org.springframework.data.jpa.domain.Specification;
import com.ecep.contract.ds.contract.repository.SalesOrderRepository;
import com.ecep.contract.ds.contract.service.SaleOrdersService;
import com.ecep.contract.model.SalesOrder;
import com.ecep.contract.ds.customer.model.SalesOrder;
import com.ecep.contract.vo.SalesOrderVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -27,7 +27,7 @@ import org.springframework.data.jpa.domain.Specification;
import com.ecep.contract.ds.contract.repository.SalesBillVoucherRepository;
import com.ecep.contract.ds.contract.service.SalesBillVoucherService;
import com.ecep.contract.model.SalesBillVoucher;
import com.ecep.contract.ds.customer.model.SalesBillVoucher;
import com.ecep.contract.vo.SalesBillVoucherVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -27,8 +27,8 @@ import org.springframework.data.jpa.domain.Specification;
import com.ecep.contract.ds.contract.repository.SalesOrderItemRepository;
import com.ecep.contract.ds.contract.service.SaleOrdersService;
import com.ecep.contract.ds.contract.service.SalesOrderItemService;
import com.ecep.contract.model.SalesOrder;
import com.ecep.contract.model.SalesOrderItem;
import com.ecep.contract.ds.customer.model.SalesOrder;
import com.ecep.contract.ds.customer.model.SalesOrderItem;
import com.ecep.contract.vo.SalesOrderItemVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

View File

@@ -0,0 +1,334 @@
package com.ecep.contract.integration;
import com.ecep.contract.IEntityService;
import com.ecep.contract.PageArgument;
import com.ecep.contract.PageContent;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.WebSocketConstant;
import com.ecep.contract.handler.SessionInfo;
import com.ecep.contract.model.Voable;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.service.WebSocketServerCallbackManager;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.*;
/**
* WebSocketServerCallbackManager的集成测试类用于测试与Service层的交互
*/
@SpringBootTest
class WebSocketServerCallbackManagerIntegrationTest {
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@Mock
private SessionInfo sessionInfo;
@InjectMocks
private WebSocketServerCallbackManager callbackManager;
private ObjectMapper objectMapper;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
objectMapper = new ObjectMapper();
}
/**
* 测试findAll方法的完整流程包括Service调用、VO转换和缓存
*/
@Test
void testFullWorkflow_FindAll() throws Exception {
// 准备测试数据
String serviceName = "contractService";
String methodName = "findAll";
String messageId = "test-find-all-1";
// 创建请求JSON
ObjectNode requestJson = createFindAllRequest(messageId, serviceName, methodName);
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertEquals(messageId, response.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertTrue((Boolean) response.get(WebSocketConstant.SUCCESS_FIELD_NAME));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行WebSocket回调
callbackManager.onMessage(sessionInfo, requestJson);
// 验证结果
// onMessage方法返回void不需要验证返回值
// 验证SessionInfo的send方法被调用
verify(sessionInfo, times(1)).send(any(Map.class));
}
/**
* 测试findById方法的完整流程包括缓存命中和未命中的情况
*/
@Test
void testFullWorkflow_FindById_CacheBehavior() throws Exception {
// 准备测试数据
String serviceName = "contractService";
String methodName = "findById";
String messageId = "test-find-by-id-1";
Integer id = 1;
// 第一次调用:缓存未命中
ObjectNode firstRequestJson = createFindByIdRequest(messageId, serviceName, methodName, id);
// 模拟Redis缓存未命中
when(redisTemplate.opsForValue().get(anyString())).thenReturn(null);
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertEquals(messageId, response.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertTrue((Boolean) response.get(WebSocketConstant.SUCCESS_FIELD_NAME));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行第一次调用
callbackManager.onMessage(sessionInfo, firstRequestJson);
// 验证Redis操作
verify(redisTemplate, times(1)).opsForValue().get(anyString());
verify(redisTemplate, times(1)).opsForValue().set(anyString(), any(), anyLong(), any());
// 重置mock对象
reset(redisTemplate);
reset(sessionInfo);
// 第二次调用:缓存命中
String secondMessageId = "test-find-by-id-2";
ObjectNode secondRequestJson = createFindByIdRequest(secondMessageId, serviceName, methodName, id);
// 模拟Redis缓存命中
Map<String, Object> mockCacheValue = new HashMap<>();
mockCacheValue.put("id", id);
mockCacheValue.put("name", "Mock Contract");
when(redisTemplate.opsForValue().get(anyString())).thenReturn(mockCacheValue);
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertEquals(secondMessageId, response.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertTrue((Boolean) response.get(WebSocketConstant.SUCCESS_FIELD_NAME));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行第二次调用
callbackManager.onMessage(sessionInfo, secondRequestJson);
// 验证Redis操作
verify(redisTemplate, times(1)).opsForValue().get(anyString());
verify(redisTemplate, times(0)).opsForValue().set(anyString(), any(), anyLong(), any());
}
/**
* 测试save方法的完整流程包括实体保存、VO转换和缓存清理
*/
@Test
void testFullWorkflow_Save() throws Exception {
// 准备测试数据
String serviceName = "contractService";
String methodName = "save";
String messageId = "test-save-1";
// 创建请求JSON
ObjectNode requestJson = createSaveRequest(messageId, serviceName, methodName);
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertEquals(messageId, response.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertTrue((Boolean) response.get(WebSocketConstant.SUCCESS_FIELD_NAME));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行WebSocket回调
callbackManager.onMessage(sessionInfo, requestJson);
// 验证SessionInfo的send方法被调用
verify(sessionInfo, times(1)).send(any(Map.class));
// 验证Redis缓存清理操作
// 在实际的Service实现中应该有缓存清理逻辑
}
/**
* 测试delete方法的完整流程包括实体删除和缓存清理
*/
@Test
void testFullWorkflow_Delete() throws Exception {
// 准备测试数据
String serviceName = "contractService";
String methodName = "delete";
String messageId = "test-delete-1";
Integer id = 1;
// 创建请求JSON
ObjectNode requestJson = createDeleteRequest(messageId, serviceName, methodName, id);
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertEquals(messageId, response.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertTrue((Boolean) response.get(WebSocketConstant.SUCCESS_FIELD_NAME));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行WebSocket回调
callbackManager.onMessage(sessionInfo, requestJson);
// 验证SessionInfo的send方法被调用
verify(sessionInfo, times(1)).send(any(Map.class));
// 验证Redis缓存清理操作
// 在实际的Service实现中应该有缓存清理逻辑
}
/**
* 测试错误处理机制
*/
@Test
void testErrorHandling() throws Exception {
// 准备测试数据 - 无效的JSON
String invalidJson = "{invalid json}";
// 模拟SessionInfo的send方法
doAnswer(invocation -> {
Object arg = invocation.getArgument(0);
assertNotNull(arg);
assertTrue(arg instanceof Map);
Map<String, Object> response = (Map<String, Object>) arg;
assertFalse((Boolean) response.get("success"));
assertNotNull(response.get("error"));
return null;
}).when(sessionInfo).send(any(Map.class));
// 执行WebSocket回调
callbackManager.onMessage(sessionInfo, objectMapper.readTree(invalidJson));
// 验证SessionInfo的send方法被调用
verify(sessionInfo, times(1)).send(any(Map.class));
}
/**
* 创建findAll请求JSON
*/
private ObjectNode createFindAllRequest(String messageId, String serviceName, String methodName) {
ObjectNode requestJson = objectMapper.createObjectNode();
requestJson.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
requestJson.put(WebSocketConstant.SERVICE_FIELD_NAME, serviceName);
requestJson.put(WebSocketConstant.METHOD_FIELD_NAME, methodName);
ArrayNode argumentsNode = objectMapper.createArrayNode();
ObjectNode paramsNode = objectMapper.createObjectNode();
paramsNode.put("searchText", "test");
argumentsNode.add(paramsNode);
ObjectNode pageableNode = objectMapper.createObjectNode();
pageableNode.put("pageNumber", 0);
pageableNode.put("pageSize", 10);
argumentsNode.add(pageableNode);
requestJson.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
return requestJson;
}
/**
* 创建findById请求JSON
*/
private ObjectNode createFindByIdRequest(String messageId, String serviceName, String methodName, Integer id) {
ObjectNode requestJson = objectMapper.createObjectNode();
requestJson.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
requestJson.put(WebSocketConstant.SERVICE_FIELD_NAME, serviceName);
requestJson.put(WebSocketConstant.METHOD_FIELD_NAME, methodName);
ArrayNode argumentsNode = objectMapper.createArrayNode();
argumentsNode.add(id);
requestJson.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
return requestJson;
}
/**
* 创建save请求JSON
*/
private ObjectNode createSaveRequest(String messageId, String serviceName, String methodName) {
ObjectNode requestJson = objectMapper.createObjectNode();
requestJson.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
requestJson.put(WebSocketConstant.SERVICE_FIELD_NAME, serviceName);
requestJson.put(WebSocketConstant.METHOD_FIELD_NAME, methodName);
ArrayNode argumentsNode = objectMapper.createArrayNode();
ObjectNode paramsNode = objectMapper.createObjectNode();
paramsNode.put("name", "Test Contract");
paramsNode.put("code", "CONTRACT-001");
argumentsNode.add(paramsNode);
argumentsNode.add("com.ecep.contract.model.ContractVo"); // 类型参数
requestJson.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
return requestJson;
}
/**
* 创建delete请求JSON
*/
private ObjectNode createDeleteRequest(String messageId, String serviceName, String methodName, Integer id) {
ObjectNode requestJson = objectMapper.createObjectNode();
requestJson.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, messageId);
requestJson.put(WebSocketConstant.SERVICE_FIELD_NAME, serviceName);
requestJson.put(WebSocketConstant.METHOD_FIELD_NAME, methodName);
ArrayNode argumentsNode = objectMapper.createArrayNode();
ObjectNode paramsNode = objectMapper.createObjectNode();
paramsNode.put("id", id);
argumentsNode.add(paramsNode);
requestJson.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
return requestJson;
}
}

View File

@@ -0,0 +1,254 @@
package com.ecep.contract.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.model.Voable;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 基础Service测试类提供通用的测试方法用于验证实体到VO的转换和缓存逻辑
*/
public abstract class BaseServiceTest<T extends Voable<VO>, VO> {
@Mock
protected RedisTemplate<String, Object> redisTemplate;
protected abstract IEntityService<T> getEntityService();
protected abstract QueryService<VO> getQueryService();
protected abstract VoableService<T, VO> getVoableService();
protected abstract T createMockEntity();
protected abstract VO createMockVo();
protected abstract String getRedisKeyPrefix();
protected abstract Integer getMockId();
protected abstract String getSearchText();
private ObjectMapper objectMapper;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
objectMapper = new ObjectMapper();
}
/**
* 测试findById方法验证实体-VO转换功能
*/
@Test
public void testFindById_VoConversion() {
Integer id = getMockId();
T mockEntity = createMockEntity();
VO expectedVo = createMockVo();
// 模拟Redis缓存未命中和服务方法调用
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(getEntityService().getById(id)).thenReturn(mockEntity);
when(getQueryService().findById(id)).thenReturn(expectedVo);
// 执行方法
VO result = getQueryService().findById(id);
// 验证结果
assertNotNull(result);
assertEquals(expectedVo, result);
// 验证方法调用
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(getEntityService(), times(1)).getById(id);
verify(redisTemplate, times(1)).opsForValue().set(eq(getRedisKeyPrefix() + id), eq(result), anyLong(),
any(TimeUnit.class));
}
/**
* 测试findById方法验证Redis缓存命中情况
*/
@Test
public void testFindById_CacheHit() {
Integer id = getMockId();
VO expectedVo = createMockVo();
// 模拟Redis缓存命中
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(expectedVo);
// 执行方法
VO result = getQueryService().findById(id);
// 验证结果
assertNotNull(result);
assertEquals(expectedVo, result);
// 验证方法调用 - 不应调用getById
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(getEntityService(), times(0)).getById(anyInt());
}
/**
* 测试findById方法验证实体不存在情况
*/
@Test
public void testFindById_EntityNotFound() {
Integer id = getMockId();
// 模拟Redis缓存未命中和实体不存在
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(getEntityService().getById(id)).thenReturn(null);
when(getQueryService().findById(id)).thenReturn(null);
// 执行方法
VO result = getQueryService().findById(id);
// 验证结果
assertNull(result);
// 验证方法调用
verify(redisTemplate, times(1)).opsForValue().get(getRedisKeyPrefix() + id);
verify(getEntityService(), times(1)).getById(id);
}
/**
* 测试updateByVo方法验证实体更新和缓存刷新功能
*/
@Test
public void testUpdateByVo() {
Integer id = getMockId();
T mockEntity = createMockEntity();
VO mockVo = createMockVo();
// 模拟getById方法调用
when(getEntityService().getById(id)).thenReturn(mockEntity);
when(getEntityService().save(mockEntity)).thenReturn(mockEntity);
// 执行方法
getVoableService().updateByVo(mockEntity, mockVo);
// 验证结果 - 直接检查传入的实体对象是否被更新
assertNotNull(mockEntity);
// 验证方法调用
verify(getEntityService(), times(1)).getById(id);
verify(getEntityService(), times(1)).save(mockEntity);
verify(redisTemplate, times(1)).delete(getRedisKeyPrefix() + id);
}
/**
* 测试findAll方法验证分页查询和VO转换功能
*/
@Test
public void testFindAll_Pagination() {
List<T> entityList = new ArrayList<>();
entityList.add(createMockEntity());
Page<T> page = new PageImpl<>(entityList);
// 使用默认的分页参数
Pageable pageable = PageRequest.of(0, 10);
Specification<T> spec = getEntityService().getSpecification(getSearchText());
// 模拟findAll方法调用
when(getEntityService().findAll(spec, pageable)).thenReturn(page);
// 执行方法这里需要具体的QueryService实现来验证
// 子类需要根据实际实现补充测试
// 验证方法调用
verify(getEntityService(), times(1)).findAll(spec, pageable);
}
/**
* 测试save方法后缓存清理功能
*/
@Test
public void testSave_CacheEviction() {
T mockEntity = createMockEntity();
// 模拟save方法调用
when(getEntityService().save(mockEntity)).thenReturn(mockEntity);
// 执行方法
T result = getEntityService().save(mockEntity);
// 验证结果
assertNotNull(result);
assertEquals(mockEntity, result);
// 验证方法调用
verify(getEntityService(), times(1)).save(mockEntity);
// 验证缓存清理如果Service中有实现缓存清理逻辑
// 子类需要根据实际实现补充验证
}
/**
* 测试delete方法后缓存清理功能
*/
@Test
public void testDelete_CacheEviction() {
T mockEntity = createMockEntity();
// 模拟delete方法调用
doNothing().when(getEntityService()).delete(mockEntity);
// 执行方法
getEntityService().delete(mockEntity);
// 验证方法调用
verify(getEntityService(), times(1)).delete(mockEntity);
// 验证缓存清理如果Service中有实现缓存清理逻辑
// 子类需要根据实际实现补充验证
}
/**
* 测试缓存过期设置
*/
@Test
public void testCacheExpiration() {
Integer id = getMockId();
T mockEntity = createMockEntity();
VO expectedVo = createMockVo();
// 模拟Redis缓存未命中和服务方法调用
when(redisTemplate.opsForValue().get(getRedisKeyPrefix() + id)).thenReturn(null);
when(getEntityService().getById(id)).thenReturn(mockEntity);
when(getQueryService().findById(id)).thenReturn(expectedVo);
// 执行方法
VO result = getQueryService().findById(id);
// 验证缓存设置了过期时间
verify(redisTemplate, times(1)).opsForValue().set(
eq(getRedisKeyPrefix() + id),
eq(result),
anyLong(),
eq(TimeUnit.MINUTES));
}
}

View File

@@ -0,0 +1,505 @@
package com.ecep.contract.service;
import com.ecep.contract.IEntityService;
import com.ecep.contract.PageArgument;
import com.ecep.contract.PageContent;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.WebSocketConstant;
import com.ecep.contract.handler.SessionInfo;
import com.ecep.contract.model.Voable;
import com.ecep.contract.service.VoableService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.MockitoAnnotations;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* WebSocketServerCallbackManager的测试类用于验证WebSocket服务组件的功能
*/
class WebSocketServerCallbackManagerTest {
@Mock
private ObjectMapper objectMapper;
@InjectMocks
private WebSocketServerCallbackManager callbackManager;
@Mock
private SessionInfo sessionInfo;
private ObjectMapper realObjectMapper;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
realObjectMapper = new ObjectMapper();
}
/**
* 测试handleAsMessageCallback方法验证正确处理findAll消息
*/
@Test
void testHandleAsMessageCallback_FindAll() throws Exception {
// 准备测试数据
ObjectNode jsonNode = realObjectMapper.createObjectNode();
jsonNode.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, "test-message-id");
jsonNode.put(WebSocketConstant.SERVICE_FIELD_NAME, "mockService");
jsonNode.put(WebSocketConstant.METHOD_FIELD_NAME, "findAll");
// 创建参数数组
ArrayNode argumentsNode = realObjectMapper.createArrayNode();
ObjectNode paramsNode = realObjectMapper.createObjectNode();
paramsNode.put("searchText", "test");
argumentsNode.add(paramsNode);
ObjectNode pageableNode = realObjectMapper.createObjectNode();
pageableNode.put("pageNumber", 0);
pageableNode.put("pageSize", 10);
argumentsNode.add(pageableNode);
jsonNode.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
// 创建模拟服务
MockQueryService mockService = mock(MockQueryService.class);
List<MockVo> voList = new ArrayList<>();
MockVo mockVo = new MockVo();
mockVo.setId(1);
mockVo.setName("test-name");
voList.add(mockVo);
Page<MockVo> page = new PageImpl<>(voList);
when(mockService.findAll(any(JsonNode.class), any(Pageable.class))).thenReturn(page);
// 模拟SpringApp.getBean
try (MockedStatic<SpringApp> mockedStatic = mockStatic(SpringApp.class)) {
mockedStatic.when(() -> SpringApp.getBean("mockService")).thenReturn(mockService);
// 模拟objectMapper.treeToValue
when(objectMapper.treeToValue(pageableNode, PageArgument.class)).thenReturn(new PageArgument());
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"handleAsMessageCallback", SessionInfo.class, String.class, JsonNode.class);
method.setAccessible(true);
Object result = method.invoke(callbackManager, sessionInfo, "test-message-id", jsonNode);
// 验证结果
assertNotNull(result);
assertTrue(result instanceof PageContent);
// 验证方法调用
verify(mockService, times(1)).findAll(any(JsonNode.class), any(Pageable.class));
}
}
/**
* 测试handleAsMessageCallback方法验证正确处理findById消息
*/
@Test
void testHandleAsMessageCallback_FindById() throws Exception {
// 准备测试数据
ObjectNode jsonNode = realObjectMapper.createObjectNode();
jsonNode.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, "test-message-id");
jsonNode.put(WebSocketConstant.SERVICE_FIELD_NAME, "mockService");
jsonNode.put(WebSocketConstant.METHOD_FIELD_NAME, "findById");
// 创建参数数组
ArrayNode argumentsNode = realObjectMapper.createArrayNode();
argumentsNode.add(1); // ID参数
jsonNode.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
// 创建模拟服务
MockEntityService mockService = mock(MockEntityService.class);
MockModel mockModel = new MockModel();
mockModel.setId(1);
mockModel.setName("test-name");
when(mockService.getById(1)).thenReturn(mockModel);
// 模拟SpringApp.getBean
try (MockedStatic<SpringApp> mockedStatic = mockStatic(SpringApp.class)) {
mockedStatic.when(() -> SpringApp.getBean("mockService")).thenReturn(mockService);
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"handleAsMessageCallback", SessionInfo.class, String.class, JsonNode.class);
method.setAccessible(true);
Object result = method.invoke(callbackManager, sessionInfo, "test-message-id", jsonNode);
// 验证结果
assertNotNull(result);
assertEquals(mockModel, result);
// 验证方法调用
verify(mockService, times(1)).getById(1);
}
}
/**
* 测试handleAsMessageCallback方法验证正确处理save消息
*/
@Test
void testHandleAsMessageCallback_Save() throws Exception {
// 准备测试数据
ObjectNode jsonNode = realObjectMapper.createObjectNode();
jsonNode.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, "test-message-id");
jsonNode.put(WebSocketConstant.SERVICE_FIELD_NAME, "mockService");
jsonNode.put(WebSocketConstant.METHOD_FIELD_NAME, "save");
// 创建参数数组
ArrayNode argumentsNode = realObjectMapper.createArrayNode();
ObjectNode paramsNode = realObjectMapper.createObjectNode();
paramsNode.put("name", "test-name");
argumentsNode.add(paramsNode);
argumentsNode.add("com.ecep.contract.service.WebSocketServerCallbackManagerTest$MockVo"); // 类型参数(内部类使用$符号)
jsonNode.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
// 创建具体服务实现
ConcreteMockEntityService mockService = new ConcreteMockEntityService();
MockModel mockModel = new MockModel();
mockModel.setId(1);
mockModel.setName("test-name");
// 模拟SpringApp.getBean
try (MockedStatic<SpringApp> mockedStatic = mockStatic(SpringApp.class)) {
mockedStatic.when(() -> SpringApp.getBean("mockService")).thenReturn(mockService);
// 模拟objectMapper.treeToValue和updateValue
MockVo mockVo = new MockVo();
mockVo.setName("test-name");
when(objectMapper.convertValue(paramsNode, MockVo.class)).thenReturn(mockVo);
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"handleAsMessageCallback", SessionInfo.class, String.class, JsonNode.class);
method.setAccessible(true);
Object result = method.invoke(callbackManager, sessionInfo, "test-message-id", jsonNode);
// 验证结果
assertNotNull(result);
assertTrue(result instanceof MockModel);
assertEquals(mockModel.getName(), ((MockModel)result).getName());
}
}
/**
* 测试handleAsMessageCallback方法验证正确处理delete消息
*/
@Test
void testHandleAsMessageCallback_Delete() throws Exception {
// 准备测试数据
ObjectNode jsonNode = realObjectMapper.createObjectNode();
jsonNode.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, "test-message-id");
jsonNode.put(WebSocketConstant.SERVICE_FIELD_NAME, "mockService");
jsonNode.put(WebSocketConstant.METHOD_FIELD_NAME, "delete");
// 创建参数数组
ArrayNode argumentsNode = realObjectMapper.createArrayNode();
ObjectNode paramsNode = realObjectMapper.createObjectNode();
paramsNode.put("id", 1);
argumentsNode.add(paramsNode);
jsonNode.set(WebSocketConstant.ARGUMENTS_FIELD_NAME, argumentsNode);
// 创建模拟服务
MockEntityService mockService = mock(MockEntityService.class);
MockModel mockModel = new MockModel();
mockModel.setId(1);
mockModel.setName("test-name");
when(mockService.getById(1)).thenReturn(mockModel);
doNothing().when(mockService).delete(mockModel);
// 模拟SpringApp.getBean
try (MockedStatic<SpringApp> mockedStatic = mockStatic(SpringApp.class)) {
mockedStatic.when(() -> SpringApp.getBean("mockService")).thenReturn(mockService);
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"handleAsMessageCallback", SessionInfo.class, String.class, JsonNode.class);
method.setAccessible(true);
Object result = method.invoke(callbackManager, sessionInfo, "test-message-id", jsonNode);
// 验证结果
assertNotNull(result);
assertEquals(mockModel, result);
// 验证方法调用
verify(mockService, times(1)).getById(1);
verify(mockService, times(1)).delete(mockModel);
}
}
/**
* 测试send方法验证消息发送功能
*/
@Test
void testSend() throws Exception {
// 准备测试数据
MockModel mockModel = new MockModel();
mockModel.setId(1);
mockModel.setName("test-name");
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"send", SessionInfo.class, String.class, Object.class);
method.setAccessible(true);
method.invoke(callbackManager, sessionInfo, "test-message-id", mockModel);
// 验证方法调用
Map<String, Object> expectedMap = new HashMap<>();
expectedMap.put("data", mockModel);
expectedMap.put(WebSocketConstant.MESSAGE_ID_FIELD_NAME, "test-message-id");
expectedMap.put(WebSocketConstant.SUCCESS_FIELD_NAME, true);
verify(sessionInfo, times(1)).send(expectedMap);
}
/**
* 测试send方法验证Voable对象的处理
*/
@Test
void testSend_Voable() throws Exception {
// 准备测试数据
MockModelWithVo mockModel = new MockModelWithVo();
mockModel.setId(1);
mockModel.setName("test-name");
// 使用反射调用私有方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod(
"send", SessionInfo.class, String.class, Object.class);
method.setAccessible(true);
method.invoke(callbackManager, sessionInfo, "test-message-id", mockModel);
// 使用ArgumentCaptor捕获实际传入的参数
ArgumentCaptor<Map<String, Object>> captor = ArgumentCaptor.forClass(Map.class);
verify(sessionInfo, times(1)).send(captor.capture());
// 验证捕获的参数
Map<String, Object> actualMap = captor.getValue();
assertEquals("test-message-id", actualMap.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME));
assertEquals(true, actualMap.get(WebSocketConstant.SUCCESS_FIELD_NAME));
// 验证data字段是MockVo类型
assertTrue(actualMap.get("data") instanceof MockVo);
MockVo actualVo = (MockVo) actualMap.get("data");
// 验证MockVo的内容
MockVo expectedVo = mockModel.toVo();
assertEquals(expectedVo.getId(), actualVo.getId());
assertEquals(expectedVo.getName(), actualVo.getName());
}
/**
* 测试createNewEntity方法验证新实体创建功能
*/
@Test
void testCreateNewEntity() throws Exception {
// 创建模拟服务
ConcreteMockEntityService mockService = new ConcreteMockEntityService();
// 使用反射获取createNewEntity方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod("createNewEntity", IEntityService.class);
method.setAccessible(true);
// 执行方法
Object result = method.invoke(callbackManager, mockService);
// 验证结果
assertNotNull(result);
assertTrue(result instanceof MockModel);
}
/**
* 测试getTargetClass方法验证获取原始类功能
*/
@Test
void testGetTargetClass() throws Exception {
// 创建模拟CGLIB代理类
Class<?> cglibProxyClass = createMockCglibProxyClass();
// 使用反射获取getTargetClass方法
Method method = WebSocketServerCallbackManager.class.getDeclaredMethod("getTargetClass", Class.class);
method.setAccessible(true);
// 执行方法
Class<?> result = (Class<?>) method.invoke(callbackManager, cglibProxyClass);
// 验证结果
assertNotNull(result);
assertEquals(MockModel.class, result);
}
/**
* 模拟的QueryService实现
*/
private interface MockQueryService extends QueryService<MockVo> {
@Override
Page<MockVo> findAll(JsonNode paramsNode, Pageable pageable);
@Override
MockVo findById(Integer id);
}
/**
* 模拟的IEntityService实现
*/
private interface MockEntityService extends IEntityService<MockModel>, VoableService<MockModel, MockVo> {
@Override
MockModel getById(Integer id);
@Override
Page<MockModel> findAll(Specification<MockModel> spec, Pageable pageable);
@Override
Specification<MockModel> getSpecification(String searchText);
@Override
void delete(MockModel entity);
@Override
MockModel save(MockModel entity);
@Override
void updateByVo(MockModel model, MockVo vo);
}
/**
* 具体的MockEntityService实现用于测试createNewEntity方法
*/
private static class ConcreteMockEntityService implements IEntityService<MockModel>, VoableService<MockModel, MockVo> {
@Override
public MockModel getById(Integer id) {
return null;
}
@Override
public Page<MockModel> findAll(Specification<MockModel> spec, Pageable pageable) {
return null;
}
@Override
public Specification<MockModel> getSpecification(String searchText) {
return null;
}
@Override
public void delete(MockModel entity) {
}
@Override
public MockModel save(MockModel entity) {
return entity; // 返回传入的实体对象
}
@Override
public void updateByVo(MockModel model, MockVo vo) {
if (model != null && vo != null) {
model.setName(vo.getName());
if (vo.getId() != null) {
model.setId(vo.getId());
}
}
}
}
/**
* 模拟的模型类
*/
public static class MockModel {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 模拟的VO类
*/
private static class MockVo {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 实现了Voable接口的模拟模型类
*/
private static class MockModelWithVo extends MockModel implements Voable<MockVo> {
@Override
public MockVo toVo() {
MockVo vo = new MockVo();
vo.setId(getId());
vo.setName(getName());
return vo;
}
}
/**
* 创建模拟的CGLIB代理类
*/
private Class<?> createMockCglibProxyClass() {
// 这里创建一个简单的模拟CGLIB代理类实际项目中可能需要更复杂的实现
return MockModel$$SpringCGLIB$$0.class;
}
/**
* 模拟的CGLIB代理类
*/
private static class MockModel$$SpringCGLIB$$0 extends MockModel {
}
}

View File

@@ -0,0 +1,256 @@
package com.ecep.contract.util;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import java.util.HashSet;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* RedisCacheCleaner的测试类用于验证缓存清理功能
*/
class RedisCacheCleanerTest {
@Mock
private RedisTemplate<String, Object> redisTemplate;
@InjectMocks
private RedisCacheCleaner cacheCleaner;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
/**
* 测试cleanCacheByPattern方法验证按模式清理缓存功能
*/
@Test
void testCleanCacheByPattern() {
// 准备测试数据
String pattern = "contract:*";
Set<String> keys = new HashSet<>();
keys.add("contract:1");
keys.add("contract:2");
keys.add("contract:3");
// 模拟RedisTemplate的scan和delete方法
when(redisTemplate.keys(anyString())).thenReturn(keys);
when(redisTemplate.delete(anyCollection())).thenReturn(1L);
// 执行方法
int result = cacheCleaner.cleanCacheByPattern(pattern);
// 验证结果
assertEquals(3, result);
// 验证方法调用
verify(redisTemplate, times(1)).keys(pattern);
verify(redisTemplate, times(1)).delete(keys);
}
/**
* 测试cleanCacheByPattern方法验证没有匹配键的情况
*/
@Test
void testCleanCacheByPattern_NoKeysFound() {
// 准备测试数据
String pattern = "non_existent:*";
// 模拟RedisTemplate的keys方法返回空集合
when(redisTemplate.keys(anyString())).thenReturn(new HashSet<>());
// 执行方法
int result = cacheCleaner.cleanCacheByPattern(pattern);
// 验证结果
assertEquals(0, result);
// 验证方法调用
verify(redisTemplate, times(1)).keys(pattern);
verify(redisTemplate, times(0)).delete(anyCollection());
}
/**
* 测试cleanCacheByPattern方法验证RedisTemplate返回null的情况
*/
@Test
void testCleanCacheByPattern_NullKeys() {
// 准备测试数据
String pattern = "test:*";
// 模拟RedisTemplate的keys方法返回null
when(redisTemplate.keys(anyString())).thenReturn(null);
// 执行方法
int result = cacheCleaner.cleanCacheByPattern(pattern);
// 验证结果
assertEquals(0, result);
// 验证方法调用
verify(redisTemplate, times(1)).keys(pattern);
verify(redisTemplate, times(0)).delete(anyCollection());
}
/**
* 测试cleanAllCache方法验证清理所有缓存功能
*/
@Test
void testCleanAllCache() {
// 准备测试数据
Set<String> keys = new HashSet<>();
keys.add("contract:1");
keys.add("project:1");
keys.add("user:1");
// 模拟RedisTemplate的scan和delete方法
when(redisTemplate.keys("*"))
.thenReturn(keys)
.thenReturn(new HashSet<>()); // 第二次调用返回空,结束循环
when(redisTemplate.delete(anyCollection())).thenReturn(1L);
// 执行方法
int result = cacheCleaner.cleanAllCache();
// 验证结果
assertEquals(3, result);
// 验证方法调用
verify(redisTemplate, atLeastOnce()).keys("*");
verify(redisTemplate, times(1)).delete(keys);
}
/**
* 测试cleanCache方法验证清理指定键的缓存功能
*/
@Test
void testCleanCache() {
// 准备测试数据
String key = "contract:1";
// 模拟RedisTemplate的delete方法
when(redisTemplate.delete(anyString())).thenReturn(true);
// 执行方法
boolean result = cacheCleaner.cleanCache(key);
// 验证结果
assertTrue(result);
// 验证方法调用
verify(redisTemplate, times(1)).delete(key);
}
/**
* 测试cleanCache方法验证清理不存在键的缓存功能
*/
@Test
void testCleanCache_KeyNotFound() {
// 准备测试数据
String key = "non_existent:1";
// 模拟RedisTemplate的delete方法返回false
when(redisTemplate.delete(anyString())).thenReturn(false);
// 执行方法
boolean result = cacheCleaner.cleanCache(key);
// 验证结果
assertFalse(result);
// 验证方法调用
verify(redisTemplate, times(1)).delete(key);
}
/**
* 测试cleanMultipleKeys方法验证清理多个键的缓存功能
*/
@Test
void testCleanMultipleKeys() {
// 准备测试数据
String[] keys = {"contract:1", "contract:2", "contract:3"};
// 模拟RedisTemplate的delete方法
when(redisTemplate.delete(anyCollection())).thenReturn(3L);
// 执行方法
long result = cacheCleaner.cleanMultipleKeys(keys);
// 验证结果
assertEquals(3, result);
// 验证方法调用
verify(redisTemplate, times(1)).delete(anyCollection());
}
/**
* 测试cleanServiceCache方法验证清理特定服务的缓存功能
*/
@Test
void testCleanServiceCache() {
// 准备测试数据
String serviceName = "ContractService";
Set<String> keys = new HashSet<>();
keys.add("contract:1");
keys.add("contract:2");
// 模拟RedisTemplate的keys和delete方法
when(redisTemplate.keys("contract:*")).thenReturn(keys);
when(redisTemplate.delete(anyCollection())).thenReturn(1L);
// 执行方法
int result = cacheCleaner.cleanServiceCache(serviceName);
// 验证结果
assertEquals(2, result);
// 验证方法调用
verify(redisTemplate, times(1)).keys("contract:*");
verify(redisTemplate, times(1)).delete(keys);
}
/**
* 测试cleanAllServiceCache方法验证清理所有服务相关缓存功能
*/
@Test
void testCleanAllServiceCache() {
// 准备测试数据
String[] serviceNames = {"ContractService", "ProjectService", "UserService"};
Set<String> contractKeys = new HashSet<>();
contractKeys.add("contract:1");
Set<String> projectKeys = new HashSet<>();
projectKeys.add("project:1");
Set<String> userKeys = new HashSet<>();
userKeys.add("user:1");
// 模拟RedisTemplate的keys和delete方法
when(redisTemplate.keys("contract:*")).thenReturn(contractKeys);
when(redisTemplate.keys("project:*")).thenReturn(projectKeys);
when(redisTemplate.keys("user:*")).thenReturn(userKeys);
when(redisTemplate.delete(anyCollection())).thenReturn(1L);
// 执行方法
int result = cacheCleaner.cleanAllServiceCache(serviceNames);
// 验证结果
assertEquals(3, result);
// 验证方法调用
verify(redisTemplate, times(1)).keys("contract:*");
verify(redisTemplate, times(1)).keys("project:*");
verify(redisTemplate, times(1)).keys("user:*");
verify(redisTemplate, times(3)).delete(anyCollection());
}
}