@SpringBootTest с MapStruct требует Impl

#java #spring-boot-test #mapstruct

#java #spring-boot-test #mapstruct

Вопрос:

У меня есть следующий тест:

 @SpringBootTest(classes = {SomeService.class, DtoMapperImpl.class})
class SomeServiceTest {
  

И следующий mapper:

 @Mapper(componentModel = "spring")
public interface DtoMapper {
    EntityDto toDto(Entity entity);
}
  

Я не меняю пакеты (это означает, что DtoMapperImpl находится в том же пакете, что и DtoMapper)

Как только я меняю Impl на интерфейс, мой тест завершается неудачей:

 @SpringBootTest(classes = {SomeService.class, DtoMapper.class})
class SomeServiceTest {
  

Вызвано:
org.springframework.beans.factory.Исключение UnsatisfiedDependencyException:
Ошибка при создании компонента с именем ‘SomeService’: неудовлетворенная зависимость
, выраженная через параметр конструктора 2; вложенным исключением является
org.springframework.beans.factory.Исключение NoSuchBeanDefinitionException: отсутствует
соответствующий компонент типа ‘DtoMapper’: ожидается по крайней мере 1
компонент, который квалифицируется как кандидат на автоматическое подключение. Аннотации зависимостей: {}

Не могли бы вы посоветовать лучший способ решения этой проблемы? Я нахожусь на MapStruct 1.3.1.Final

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

1. Потому что вы используете @SpringBootTest неправильно. То есть для интеграционных тестов spring boot вы хотите протестировать свой сервис, а затем не использовать @SpringBootTest его для этого.

2. Извините, это не отвечает на мой вопрос

3. Как указано, вы используете неправильный инструмент для задания или в данном случае используете инструмент неправильно. Вы либо пишете полный интеграционный тест (а затем удаляете classes часть), ЛИБО пишете простой модульный тест для своего сервиса и вручную вводите mapper, получая его из самого MapStruct.

4. Извините, просто хотел отметить, что ваши комментарии не по теме в этом вопросе. Если вы хотите обсудить идеологию, как писать интеграционные тесты, я буду рад сделать это вне этого вопроса. Моды, пожалуйста, очистите

5. Речь идет не об идеологии, а об использовании неправильного инструмента для вашего теста, что я и пытаюсь объяснить.

Ответ №1:

Проблема на самом деле не имеет ничего общего с MapStruct, а скорее с тем, как SpringBootTest#classes он используется.

classes In SpringBootTest предназначен для предоставления ваших компонентов, которые должны использоваться для загрузки в тесте.

Из JavaDoc:

Классы компонентов, используемые для загрузки ApplicationContext . Также может быть указано с помощью @ContextConfiguration(classes=...) . Если явные классы не определены, тест будет искать вложенные @Configuration классы, прежде чем вернуться к @SpringBootConfiguration поиску. Возвращает: классы компонентов, используемые для загрузки контекста приложения

В вашем случае у вас есть 2 класса:

  • SomeService — я предполагаю, что это класс, аннотированный, @Service и Spring правильно загрузит его
  • DtoMapper — это MapStruct mapper, который является интерфейсом, а не компонентом. Компонент, который вы хотите использовать для своих тестов, является DtoMapperImpl

У вас есть несколько вариантов, чтобы исправить это:

Используйте класс Impl

Вы можете использовать DtoMapperImpl (класс Spring Component) в своем SpringBootTest#classes тесте, после чего ваш тест загрузит правильный компонент

Используйте пользовательский класс конфигурации, который будет сканировать ваши картографы

 @TestConfiguration
@ComponentScan("com.example.mapper")
public class MappersConfig {

}
  

А затем используйте это в своем SpringBootTest#classes . Например.

 @SpringBootTest(classes = {SomeService.class, MappersConfig.class})
class SomeServiceTest {
   ...
}
  

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

1. Спасибо, это то, что я опубликовал ранее. Не могли бы вы скопировать мой пример кода во 2-м варианте, и я отмечу это как ответ?

Ответ №2:

Создайте следующую конфигурацию (должна указывать, где находятся картографы):

 @TestConfiguration
@ComponentScan("some.package.mapper")
public class MappersConfig {
}
  

И изменить фрагмент:

 @SpringBootTest(classes = {SomeService.class, MappersConfig.class})
class SomeServiceTest {
  

Ответ №3:

Я бы предложил небольшое улучшение для принятого ответа, чтобы вам не нужно было записывать имя пакета в виде жестко заданной строки, но вместо этого используйте ComponentScan basePackageClasses:

 @TestConfiguration
// @ComponentScan("some.package.mapper")
@ComponentScan(basePackageClasses = DtoMapper.class)
public class MappersConfig {
}
  

Но в этом (а также в принятом ответе) подходе есть очевидный недостаток: будет проверен весь пакет, который может содержать нежелательные классы.