如何在Mockito测试中调用真实的MapStruct Mapper方法?
我有一个服务类MyServiceImpl,其方法通过MyMapper将实体列表转换为DTO列表返回。我希望在测试中调用真实的Mapper方法以验证实际功能。
当前测试类使用@ExtendWith(MockitoExtension.class),对MyMapper标注@Mock(answer = Answers.CALLS_REAL_METHODS)并注入到待测试的MyServiceImpl中。测试时调用getActiveUserCardsById方法,预期返回4个DTO,但实际得到空列表。
请问如何仅通过MockitoExtension调用真实的MapStruct Mapper方法?
测试类代码
@ExtendWith(MockitoExtension.class) public class UserCardServiceTest { @Mock private MyCardRepository myCardRepository; @Mock(answer = Answers.CALLS_REAL_METHODS) private MyMapper myMapper; @InjectMocks private MyServiceImpl underTest; @BeforeEach void setUp() { listOfExpiredUserCardEntities = new ArrayList<>(List.of( getValidUserCardEntity() .setExpirationDate(LocalDate.now().minusDays(1)) .setBlocked(false), getValidUserCardEntity() .setExpirationDate(LocalDate.now().minusWeeks(1)) .setBlocked(false), getValidUserCardEntity() .setExpirationDate(LocalDate.now().minusYears(1)) .setBlocked(false), getValidUserCardEntity() .setExpirationDate(LocalDate.now().minusMonths(1)) .setBlocked(false)); } @Test void shouldChangeStatusIfCardExpired() { given(myCardRepository.findAllByUserId(1L)) .willReturn(listOfCardEntities); assertThat(underTest.getActiveUserCardsById(1L)).isNotNull().hasSize(4); assertThat(underTest.getActiveUserCardsById(1)) .extracting(CardDto::isBlocked) .containsExactly(true, true, true, true) .doesNotContainNull(); } }
服务类方法代码
@RequiredArgsConstructor public class MyServiceImpl implements MyCardService { private final MyCardRepository myCardRepository; private final MyMapper myMapper; @Override public List<CardDto> getActiveUserCardsById(final long userId) { List<CardEntity> listOfEntities = myRepository.findAllByUserId(userId); listOfEntities.stream().filter(validationService::isCardExpired) .forEach(userCard -> userCard.setBlocked(true)); List<CardEntity> sorted = listOfEntities.stream() .sorted(o -> o.getName().toUpperCase()) .toList(); return myMapper .listOfCardEntitiesToCardDto(sorted); } }
测试报错信息
java.lang.AssertionError: Expected size: 4 but was: 0 in: []
核心问题是:MapStruct的MyMapper是接口,Mockito的@Mock(answer = Answers.CALLS_REAL_METHODS)对接口无效——接口本身没有方法实现,代理对象调用时只会返回默认值(比如空列表),这就是你得到空结果的原因。
要在MockitoExtension环境中调用真实的MapStruct Mapper方法,有两种可靠方式:
方式一:直接使用真实的Mapper实现类实例
MapStruct会自动生成Mapper接口的实现类(默认命名为[Mapper接口名]Impl,比如MyMapperImpl),直接创建该实例并注入服务即可:
@ExtendWith(MockitoExtension.class) public class UserCardServiceTest { @Mock private MyCardRepository myCardRepository; // 直接实例化真实的Mapper实现类 private MyMapper myMapper = new MyMapperImpl(); @InjectMocks private MyServiceImpl underTest; // 其余测试代码保持不变 }
方式二:用Mockito @Spy包装真实实例
如果需要保留Mockito的部分特性(比如偶尔打桩特定方法),可以用@Spy包装真实Mapper实例,默认会调用真实方法:
@ExtendWith(MockitoExtension.class) public class UserCardServiceTest { @Mock private MyCardRepository myCardRepository; // @Spy包装真实实例,默认执行真实方法逻辑 @Spy private MyMapper myMapper = new MyMapperImpl(); @InjectMocks private MyServiceImpl underTest; // 其余测试代码保持不变 }
额外修复点
你的测试代码中还存在变量名不一致问题:setUp方法初始化的是listOfExpiredUserCardEntities,但测试中given语句返回的是listOfCardEntities,这会导致仓库返回空列表,需要把变量名统一,比如将listOfExpiredUserCardEntities改为listOfCardEntities。
内容的提问来源于stack exchange,提问作者idelkaro

