Весенняя загрузка — @MockBean Создает дубликат в тестовом примере

#java #spring #spring-boot #spring-test

Вопрос:

Я пытаюсь «поиздеваться» над бобом @SpringBootTest .

 @SpringBootTest
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
public class SalesSummaryServiceTest {

    @MockBean
    private SalesSummaryMapper summmaryMapper;
    
    @Autowired
    private SalesSummaryService salesSummmaryService;

    @Test
    public void testGetMonthlySummary() {

        // Removed for brevity

        given(this.summmaryMapper.selectByPropDate(testSchema, testProp, testDate, testEnd)).willReturn(List.of(testSum, testSum));     
        var res = this.salesSummmaryService.getMonthlySummary(test, testProp, testDate.getMonthValue(), testDate.getYear());

        // Removed for brevity
        
        
        
    }

}
 

Однако тест завершается неудачно (контекст приложения не загружается), поскольку не удается найти уникальный компонент:

 Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.midamcorp.data.dal.SalesSummaryMapper' available: expected single matching bean but found 2: salesSummaryMapper,com.midamcorp.data.dal.SalesSummaryMapper#0
 

Тестируемый класс является базовой @Service реализацией:

 @Service
public class SalesSummaryServiceImpl implements SalesSummaryService {

    private SalesSummaryMapper salesSumMapper;
    
    @Autowired
    public SalesSummaryServiceImpl(SalesSummaryMapper salesSumMapper) {
        this.salesSumMapper = salesSumMapper;
    }

    @Override
    public List<SaleSummary> getMonthlySummary(PropType propType, String propId, int monthVal, int yearVal) {
        var startDate = LocalDate.of(yearVal, monthVal, 1);
        var endDate = startDate.with(TemporalAdjusters.lastDayOfMonth());
        return this.salesSumMapper.selectByPropDate(propType.getSchema(), propId, startDate, endDate);
    }
}
 

SaleSummaryMapper Зависимость-это простой картограф MyBatis:

 import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SalesSummaryMapper {
}
 

Я понимаю основную ошибку (например, Spring не может определить, какой компонент использовать, поскольку два могут удовлетворить зависимость). Чего я не понимаю, так это почему «реальный» компонент картографа не заменяется на @MockBean когда, согласно документам: «Любой существующий отдельный компонент того же типа, определенный в контексте, будет заменен макетом». Что я упускаю? Спасибо!

Комментарии:

1. Если вы хотите только протестировать этот тест, не используйте @SpringBootTest его . Просто создайте экземпляр службы, вручную имитируйте зависимость и запустите тест. Намного быстрее и проще. Используйте только @SpringBootTest в том случае, если вы хотите провести полный интеграционный тест.

Ответ №1:

Я думаю, что проблема может быть связана с тем, как MyBatis регистрирует бобы в контексте весны. Интерфейс SalesSummaryMapper не является типом компонента, созданного в контексте spring. Из примера документации MyBatis я вижу, что картограф на самом деле имеет тип MapperFactoryBean :

 <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
 

Я предполагаю, что подобное происходит, когда вы используете любой сканер для регистрации интерфейсов, отмеченных @Mapper аннотациями.

Таким образом, основная причина @MockBean , по которой картограф не заменяет картограф, заключается в том, что они не одного типа.

Правка: Я беру вышесказанное обратно. Бобы будут того же типа, что и у MapperFactoryBean Весны FactoryBean . Может быть, в тот момент, когда создается издевательский боб, на фабрике еще не создан боб?

Сначала вы можете проверить это, заменив свой картограф на реальный @Component компонент. Если это сработает, то вам следует попытаться издеваться, используя имена, а не типы.

В документации MyBatis вы можете прочитать:

Обнаруженные сопоставители будут названы с использованием стратегии именования Spring по умолчанию для автоматически обнаруженных компонентов (см. справочный документ Spring(Основные технологии-Именование автоматически обнаруженных компонентов-)). То есть, если аннотация не найдена, она будет использовать некапитализированное неквалифицированное имя класса картографа. Но если будет найден @Компонент или @Именованная аннотация JSR-330, она получит имя из аннотации. Обратите внимание, что вы можете установить атрибут аннотации в org.springframework.stereotype.Компонент, javax.inject.По имени (если у вас есть JSE 6) или к вашей собственной аннотации (которая сама должна быть аннотирована), поэтому аннотация будет работать как в качестве маркера, так и в качестве поставщика имен.

Исходя из вышесказанного, потенциальным решением может быть дополнительная пометка картографа как названного @Component :

 @Mapper
@Component("salesSummaryMapper")
public interface SalesSummaryMapper {
}
 

и в тестовом использовании @Qualifier

 @MockBean
@Qualifier("salesSummaryMapper")
private SalesSummaryMapper summmaryMapper;
 

Комментарии:

1. Заводской компонент создаст указанный компонент. Что, вероятно, является причиной наличия 2 бобов здесь в этот момент.