Я хотел бы внедрить фиктивный объект Mockito в bean-компонент Spring (3+) для модульного тестирования с помощью JUnit. Мои зависимости bean-компонентов в настоящее время внедряются с использованием @Autowired
аннотации для закрытых полей-членов.
Я рассмотрел использование, ReflectionTestUtils.setField
но экземпляр компонента, который я хочу внедрить, на самом деле является прокси и, следовательно, не объявляет поля закрытого члена целевого класса. Я не хочу создавать общедоступный установщик для зависимости, так как тогда я буду модифицировать свой интерфейс исключительно для целей тестирования.
Я следовал некоторым советам, данным сообществом Spring, но макет не создается, и автоматическое подключение не работает:
<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.package.Dao" />
</bean>
Ошибка, с которой я сейчас сталкиваюсь, такова:
...
Caused by: org...NoSuchBeanDefinitionException:
No matching bean of type [com.package.Dao] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {
@org...Autowired(required=true),
@org...Qualifier(value=dao)
}
at org...DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(D...y.java:901)
at org...DefaultListableBeanFactory.doResolveDependency(D...y.java:770)
Если я установил constructor-arg
значение на что-то недопустимое, то при запуске контекста приложения не будет ошибок.
Ответы:
Лучший способ это:
Обновление
В файле контекста этот макет должен быть указан до того, как будет объявлено любое поле с автопроводкой, в зависимости от того, какое оно будет.
источник
Это добавит любые проверенные объекты в тестовый класс. В этом случае он вставит mockedObject в testObject. Это было упомянуто выше, но вот код.
источник
mockedObject
?Mockito.spy(...)
этоmockedObject
вместо этого? А затем используйтеwhen(mockedObject.execute).thenReturn(objToReturn)
илиdoReturn(objToReturn).when(mockedObject).execute()
. Второй не вызывает реальный метод. Вы также можете проверитьMockito.doCallRealMethod()
документациюУ меня есть очень простое решение, использующее Spring Java Config и Mockito:
источник
initMocks
? Почему не толькоreturn Mockito.mock(BeanA.class)
вgetBeanA
? Так проще и меньше кода. Чего мне не хватает?Дано:
Вы можете загрузить тестируемый класс с помощью автоматической разводки, смоделировать зависимость с помощью Mockito, а затем использовать Spring ReflectionTestUtils, чтобы вставить макет в тестируемый класс.
Обратите внимание, что до Spring 4.3.1 этот метод не работал со службами за прокси-сервером (с комментариями
@Transactional
илиCacheable
, например). Это было исправлено SPR-14050 .Для более ранних версий решение состоит в том, чтобы развернуть прокси-сервер, как описано здесь: Транзакционная аннотация предотвращает мошенничество с сервисами (что сейчас и
ReflectionTestUtils.setField
делается по умолчанию)источник
Если вы используете Spring Boot 1.4, у вас есть отличный способ сделать это. Просто используйте новый бренд
@SpringBootTest
в своем классе и@MockBean
на поле, и Spring Boot создаст макет этого типа и вставит его в контекст (вместо внедрения оригинального):С другой стороны, если вы не используете Spring Boot или используете предыдущую версию, вам придется проделать немного больше работы:
Создайте
@Configuration
bean-компонент, который вставляет ваши макеты в контекст Spring:Используя
@Primary
аннотацию, вы говорите Spring, что этот бин имеет приоритет, если не указан спецификатор.Убедитесь, что вы аннотировали класс
@Profile("useMocks")
, чтобы контролировать, какие классы будут использовать макет, а какие - настоящий бин.Наконец, в вашем тесте активируйте
userMocks
профиль:Если вы не хотите использовать макет, а настоящий боб, просто не активируйте
useMocks
профиль:источник
web.xml
и настройкой AnnotationConfigWebApplicationContext. Пришлось использовать@WebAppConfiguration
вместо@WebIntegrationTest
и@ContextHierarchy
с@ContextConfiguration
вместо@SpringApplicationConfiguration
.@Primary
аннотацию для моего случая, так как внутри a произошел сбой вызова,@PostConstruct
который я хотел высказать, но@PostConstruct
bean-компонент был создан перед моей имитацией, поэтому он не использовал mock (пока я не добавил@Primary
).Начиная с версии 1.8.3 Mockito
@InjectMocks
- это невероятно полезно. Мои тесты JUnit -@RunWith
это объекты,MockitoJUnitRunner
и я создаю@Mock
объекты, которые удовлетворяют всем зависимостям для тестируемого класса, и все они внедряются, когда аннотируется закрытый член@InjectMocks
.Я для интеграции тестов только сейчас.
@RunWith
SpringJUnit4Runner
Отмечу, что он, похоже, не способен впрыскивать так
List<T>
же, как Spring. Он ищет только объект Mock, который удовлетворяетList
, и не будет вводить список объектов Mock. Обходной путь для меня состоял в том, чтобы использовать@Spy
список, созданный вручную, и вручную добавить фиктивные объекты в этот список для модульного тестирования. Возможно, это было сделано намеренно, потому что это, безусловно, заставило меня обратить пристальное внимание на то, что насмехалось вместе.источник
Обновление: теперь есть лучшие, более чистые решения этой проблемы. Пожалуйста, рассмотрите другие ответы в первую очередь.
В конце концов я нашел ответ на этот вопрос Ронена в своем блоге. Проблема, с которой я столкнулся, связана с тем, что метод
Mockito.mock(Class c)
объявляет тип возвращаемого значенияObject
. Следовательно, Spring не может определить тип компонента из возвращаемого фабричным методом.Решение Ронена состоит в том, чтобы создать
FactoryBean
реализацию, которая возвращает макеты.FactoryBean
Интерфейс позволяет Spring запрашивать тип объектов , созданных на заводе боба.Моё определение бобового бина теперь выглядит так:
источник
С весны 3.2 это больше не проблема. Spring теперь поддерживает Autowiring результатов универсальных фабричных методов. См. Раздел «Общие фабричные методы» в этом сообщении блога: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ .
Ключевым моментом является:
Что означает, что это должно работать из коробки:
источник
Приведенный ниже код работает с автопроводкой - это не самая короткая версия, но полезная, когда она должна работать только со стандартными банками с пружиной / мокито.
источник
Если вы используете spring> = 3.0 , попробуйте использовать
@Configuration
аннотацию Springs, чтобы определить часть контекста приложения.Если вы не хотите использовать @ImportResource, это можно сделать и наоборот:
Для получения дополнительной информации взгляните на spring-framework-reference: конфигурация контейнера на основе Java
источник
Возможно, не идеальное решение, но я склонен не использовать пружину, чтобы делать DI для модульных тестов. зависимости для одного компонента (тестируемого класса) обычно не слишком сложны, поэтому я просто делаю инъекцию непосредственно в тестовом коде.
источник
Я могу сделать следующее, используя Mockito:
источник
Опубликовать несколько примеров на основе вышеуказанных подходов
С весны:
Без весны:
источник
Обновление - новый ответ здесь: https://stackoverflow.com/a/19454282/411229 . Этот ответ применим только к тем версиям Spring до 3.2.
Некоторое время я искал более определенное решение для этого. Этот пост в блоге, кажется, покрывает все мои потребности и не зависит от упорядочения объявлений бобов. Все заслуги Маттиаса Северсона. http://www.jayway.com/2011/11/30/spring-integration-tests-part-i-creating-mock-objects/
В основном, реализовать FactoryBean
Затем обновите ваш весенний конфиг следующим образом:
источник
Глядя на темп развития Springockito и количество открытых вопросов , я бы немного волновался о том, чтобы внедрить его в свой набор тестовых наборов. Тот факт, что последний выпуск был сделан до выпуска Spring 4, поднимает такие вопросы, как «Возможно ли легко интегрировать его с Spring 4?». Я не знаю, потому что я не пробовал это. Я предпочитаю чистый подход Spring, если мне нужно смоделировать Spring bean в интеграционном тесте.
Существует возможность подделать Spring bean с помощью простых функций Spring. Вам нужно использовать
@Primary
,@Profile
и@ActiveProfiles
аннотацию к нему. Я написал сообщение в блоге на эту тему.источник
Я нашел аналогичный ответ как teabot, чтобы создать MockFactory, который предоставляет макеты. Я использовал следующий пример для создания фиктивной фабрики (поскольку ссылка на narkisr мертва): http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/ орг / Randompage / закладки / бэкенд / testUtils / MocksFactory.java
Это также помогает предотвратить то, что Spring хочет разрешить инъекции из осмеянного боба.
источник
это ^ отлично работает, если объявлено первым / рано в файле XML. Mockito 1.9.0 / Spring 3.0.5
источник
Я использую комбинацию подхода, используемого в ответе Маркусом Т, и простой вспомогательной реализацией,
ImportBeanDefinitionRegistrar
которая ищет пользовательскую аннотацию (@MockedBeans
), в которой можно указать, какие классы должны быть смоделированы. Я полагаю, что этот подход приводит к краткому модульному тестированию с удалением некоторого стандартного кода, связанного с имитацией.Вот как выглядит примерный модульный тест с таким подходом:
Чтобы это произошло, вам нужно определить два простых вспомогательных класса - пользовательскую аннотацию (
@MockedBeans
) и пользовательскуюImportBeanDefinitionRegistrar
реализацию.@MockedBeans
Определение аннотации должно быть аннотировано,@Import(CustomImportBeanDefinitionRegistrar.class)
иImportBeanDefinitionRgistrar
необходимо добавить определения фиктивных бинов к конфигурации в егоregisterBeanDefinitions
методе.Если вам нравится подход, вы можете найти примеры реализации в моем блоге .
источник
Я разработал решение, основанное на предложении Кресимира Несека. Я добавил новую аннотацию @EnableMockedBean , чтобы сделать код немного чище и модульным.
Я написал пост, объясняющий это.
источник
Я бы предложил перенести ваш проект на Spring Boot 1.4. После этого вы можете использовать новую аннотацию,
@MockBean
чтобы подделатьcom.package.Dao
источник
Сегодня я обнаружил, что весенний контекст, в котором я объявил перед бобами Mockito, не загружался. После перемещения ПОСЛЕ макетов контекст приложения был успешно загружен. Береги себя :)
источник
Для справки, все мои тесты корректно работают, просто делая инициализацию прибора отложенной, например:
Я полагаю, что обоснование - это то, что Маттиас объясняет здесь (внизу поста), что обходной путь изменяет порядок объявления бинов - ленивая инициализация - это «своего рода» объявление объекта в конце.
источник
Если вы используете Controller Injection, убедитесь, что ваши локальные переменные НЕ являются «окончательными»
источник