#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 бобов здесь в этот момент.