You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为泛型方法编写单元测试?无需创建特定泛型类型

如何在不创建特定泛型类型的情况下测试泛型Service类?

先贴出待测试的泛型Service代码:

public class Service<T, E> { 

  private final Class<T> domainClass;
  
  private final Class<E> entityClass;
  
  private final Repository<E> repository;

  private final AnotherService conversionService;

  // 补充构造方法(原代码未展示,测试需依赖)
  public Service(Class<T> domainClass, Class<E> entityClass, Repository<E> repository, AnotherService conversionService) {
    this.domainClass = domainClass;
    this.entityClass = entityClass;
    this.repository = repository;
    this.conversionService = conversionService;
  }

  public ResponseDto deleteAllThenImportAll(Collection<T> data) {
    Collection<E> entities = conversionService.convert(data, domainClass, entityClass);
    ResponseEntity entity = repository.deleteAllThenImportAll(entities);
    return conversionService.convert(entity);
  }
}

// 补充测试所需的依赖类定义
interface Repository<E> {
  ResponseEntity deleteAllThenImportAll(Collection<E> entities);
}

class AnotherService {
  <T, E> Collection<E> convert(Collection<T> data, Class<T> domainClass, Class<E> entityClass) {
    return null;
  }

  ResponseDto convert(ResponseEntity entity) {
    return null;
  }
}

class ResponseEntity {}
class ResponseDto {}

测试思路

Java泛型采用类型擦除机制,运行时不会保留泛型参数的具体类型信息。因此我们完全可以用通配符(<?, ?>)或者Object作为泛型参数创建Service实例,配合Mockito模拟所有依赖,只验证方法的调用逻辑是否符合预期,不需要关心T和E的具体类型。

具体测试实现(JUnit 5 + Mockito)

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Collections;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class ServiceTest {

  @Mock
  private Repository<Object> mockRepository;

  @Mock
  private AnotherService mockConversionService;

  // 直接用Object作为泛型参数创建实例,传入任意Class对象即可
  @InjectMocks
  private Service<Object, Object> service = new Service<>(Object.class, Object.class, mockRepository, mockConversionService);

  @Test
  void deleteAllThenImportAll_ShouldFollowCallFlow() {
    // 准备测试数据
    Collection<Object> testData = Collections.singletonList(new Object());
    Collection<Object> mockEntities = Collections.singletonList(new Object());
    ResponseEntity mockResponseEntity = new ResponseEntity();
    ResponseDto expectedDto = new ResponseDto();

    // 模拟依赖方法的返回值
    when(mockConversionService.convert(testData, Object.class, Object.class)).thenReturn(mockEntities);
    when(mockRepository.deleteAllThenImportAll(mockEntities)).thenReturn(mockResponseEntity);
    when(mockConversionService.convert(mockResponseEntity)).thenReturn(expectedDto);

    // 执行测试方法
    ResponseDto actualDto = service.deleteAllThenImportAll(testData);

    // 验证调用逻辑和返回值
    verify(mockConversionService).convert(testData, Object.class, Object.class);
    verify(mockRepository).deleteAllThenImportAll(mockEntities);
    verify(mockConversionService).convert(mockResponseEntity);
    assert actualDto == expectedDto;
  }
}

关键要点

  • 利用泛型擦除特性,不需要定义具体的T、E实现类,直接用Object占位即可
  • 所有依赖(Repository、AnotherService)都用Mockito模拟,避免依赖真实的类型转换或数据库操作
  • 测试核心是验证方法调用顺序和参数传递是否符合预期,而非真实的业务逻辑实现
  • 如果需要验证类型参数的正确性,可以通过ArgumentMatchers的eq方法匹配传入的Class对象

内容的提问来源于stack exchange,提问作者Black Rabbit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 08:20:54