Инъекция Мокито издевается над весенним бобаном

Я хотел бы добавить объект Mockito mock в компонент Spring (3+) для целей модульного тестирования с помощью JUnit. В настоящее время @Autowired зависимости вводятся с помощью annotations @Autowired в частных полях-членах.

Я рассмотрел использование ReflectionTestUtils.setField но экземпляр компонента, который я хотел бы вставить, на самом деле является прокси-сервером и, следовательно, не объявляет поля частного члена целевого classа. Я не хочу создавать публичный сеттер для зависимости, поскольку я буду модифицировать свой интерфейс только для целей тестирования.

Я следил за советами, предоставленными сообществом Spring, но макет не создается и автоматическая проводка выходит из строя:

    

Ошибка, с которой я сталкиваюсь, выглядит следующим образом:

 ... 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 на что-то недействительное, при запуске контекста приложения не возникает ошибки.

Лучший способ:

    

Обновить
В контекстном файле этот макет должен быть указан до того, как будет объявлено какое-либо поле с автоопределением в зависимости от его объявления.

 @InjectMocks private MyTestObject testObject; @Mock private MyDependentObject mockedObject; @Before public void setup() { MockitoAnnotations.initMocks(this); } 

Это добавит любые издеваемые объекты в тестовый class. В этом случае он будет вводить mockedObject в testObject. Это было упомянуто выше, но вот код.

У меня очень простое решение с использованием Spring Java Config и Mockito:

 @Configuration public class TestConfig { @Mock BeanA beanA; @Mock BeanB beanB; public TestConfig() { MockitoAnnotations.initMocks(this); //This is a key } //You basically generate getters and add @Bean annotation everywhere @Bean public BeanA getBeanA() { return beanA; } @Bean public BeanB getBeanB() { return beanB; } } 

Данный:

 @Service public class MyService { @Autowired private MyDAO myDAO; // etc } 

Вы можете испытать тестируемый class с помощью автосогласования, высмеивать зависимость с помощью Mockito, а затем использовать Spring ReflectTestUtils Spring, чтобы ввести макет в тестируемый class.

 @ContextConfiguration(classes = { MvcConfiguration.class }) @RunWith(SpringJUnit4ClassRunner.class) public class MyServiceTest { @Autowired private MyService myService; private MyDAO myDAOMock; @Before public void before() { myDAOMock = Mockito.mock(MyDAO.class); ReflectionTestUtils.setField(myService, "myDAO", myDAOMock); } // etc } 

Обратите внимание, что до весны 4.3.1 этот метод не будет работать с службами за прокси ( @Transactional , с помощью @Transactional или Cacheable ). Это было исправлено SPR-14050 .

Для более ранних версий решение состоит в том, чтобы развернуть прокси-сервер, как описано там: аннотация транзакции позволяет избежать издевательства сервисов (что и делает ReflectionTestUtils.setField по умолчанию сейчас)

Если вы используете Spring Boot 1.4, у него есть отличный способ сделать это. Просто используйте новый бренд @SpringBootTest для вашего classа и @MockBean на поле, а Spring Boot создаст макет этого типа, и он будет вводить его в контекст (вместо того, чтобы вводить исходный):

 @RunWith(SpringRunner.class) @SpringBootTest public class MyTests { @MockBean private RemoteService remoteService; @Autowired private Reverser reverser; @Test public void exampleTest() { // RemoteService has been injected into the reverser bean given(this.remoteService.someCall()).willReturn("mock"); String reverse = reverser.reverseSomeCall(); assertThat(reverse).isEqualTo("kcom"); } } 

С другой стороны, если вы не используете Spring Boot или используете предыдущую версию, вам придется немного поработать:

Создайте компонент @Configuration который вводит ваши @Configuration в контекст Spring:

 @Configuration @Profile("useMocks") public class MockConfigurer { @Bean @Primary public MyBean myBeanSpy() { return mock(MyBean.class); } } 

Используя @Primary аннотацию, вы говорите весне, что этот компонент имеет приоритет, если не указан спецификатор.

Убедитесь, что вы комментируете class с помощью @Profile("useMocks") , чтобы контролировать, какие classы будут использовать макет, и какие из них будут использовать реальный компонент.

Наконец, в вашем тесте активируйте профиль userMocks :

 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest @ActiveProfiles(profiles={"useMocks"}) public class YourIntegrationTestIT { @Inject private MyBean myBean; //It will be the mock! @Test public void test() { .... } } 

Если вы не хотите использовать макет, но реальный компонент, просто не активируйте профиль useMocks :

 @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {Application.class}) @WebIntegrationTest public class AnotherIntegrationTestIT { @Inject private MyBean myBean; //It will be the real implementation! @Test public void test() { .... } } 

Поскольку 1.8.3 Mockito имеет @InjectMocks – это невероятно полезно. Мои тесты JUnit: @RunWith MockitoJUnitRunner, и я создаю объекты @Mock, которые удовлетворяют всем зависимостям тестируемого classа, которые все вводятся, когда частный член аннотируется с помощью @InjectMocks.

I @RunWith SpringJUnit4Runner для тестов интеграции только сейчас.

Отмечу, что он, похоже, не может вводить List так же, как Spring. Он выглядит только для объекта Mock, который удовлетворяет списку, и не будет вводить список объектов Mock. Обходной путь для меня состоял в том, чтобы использовать @Spy для списка, созданного вручную, и вручную .add mock object (s) в этот список для модульного тестирования. Возможно, это было намеренно, потому что это, безусловно, заставило меня обратить пристальное внимание на то, что насмехалось вместе.

Обновление: теперь есть более чистые решения этой проблемы. Сначала рассмотрите другие ответы.

В конце концов я нашел ответ на этот вопрос в своем блоге. Проблема, с которой я Mockito.mock(Class c) связана с методом Mockito.mock(Class c) объявляющим возвращаемый тип Object . Следовательно, Spring не может вывести тип компонента из заводского метода возврата.

Решение Ronen заключается в создании реализации FactoryBean которая возвращает FactoryBean . Интерфейс FactoryBean позволяет Spring запрашивать тип объектов, созданных фабричным компонентом.

Мое определение фальшивки теперь выглядит так:

    

С весны 3.2 это уже не проблема. Spring теперь поддерживает Autowiring результатов общих заводских методов. См. Раздел «Общие методы фабрики» в этом блоге: http://spring.io/blog/2012/11/07/spring-framework-3-2-rc1-new-testing-features/ .

Ключевым моментом является:

В Spring 3.2 общие типы возвращаемых данных для заводских методов теперь правильно выведены, а автоустановка по типу для mocks должна работать должным образом. В результате пользовательские рабочие процессы, такие как MockitoFactoryBean, EasyMockFactoryBean или Springockito, скорее всего, больше не нужны.

Это означает, что это должно работать из коробки:

    

Ниже код работает с автоуведомлением – это не самая короткая версия, но полезная, когда она должна работать только со стандартными баночками весной / мокито.

        com.package.Dao   

Если вы используете spring> = 3.0 , попробуйте использовать аннотацию Springs @Configuration чтобы определить часть контекста приложения

 @Configuration @ImportResource("com/blah/blurk/rest-of-config.xml") public class DaoTestConfiguration { @Bean public ApplicationService applicationService() { return mock(ApplicationService.class); } } 

Если вы не хотите использовать @ImportResource, это также можно сделать наоборот:

      

Для получения дополнительной информации см. Spring-framework-reference: конфигурация контейнера на Java

Я могу сделать следующее, используя Mockito:

    

Возможно, это не идеальное решение, но я, как правило, не использую пружину для DI для модульных тестов. зависимости для одного компонента (тестируемого classа) обычно не слишком сложны, поэтому я просто делаю инъекцию непосредственно в тестовом коде.

Проводка нескольких примеров, основанных на вышеуказанных подходах

С весны:

 @ContextConfiguration(locations = { "classpath:context.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public class TestServiceTest { @InjectMocks private TestService testService; @Mock private TestService2 testService2; } 

Без весны:

 @RunWith(MockitoJUnitRunner.class) public class TestServiceTest { @InjectMocks private TestService testService = new TestServiceImpl(); @Mock private TestService2 testService2; } 

Обновление – новый ответ здесь: 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

 package com.jayway.springmock; import org.mockito.Mockito; import org.springframework.beans.factory.FactoryBean; /** * A {@link FactoryBean} for creating mocked beans based on Mockito so that they * can be {@link @Autowired} into Spring test configurations. * * @author Mattias Severson, Jayway * * @see FactoryBean * @see org.mockito.Mockito */ public class MockitoFactoryBean implements FactoryBean { private Class classToBeMocked; /** * Creates a Mockito mock instance of the provided class. * @param classToBeMocked The class to be mocked. */ public MockitoFactoryBean(Class classToBeMocked) { this.classToBeMocked = classToBeMocked; } @Override public T getObject() throws Exception { return Mockito.mock(classToBeMocked); } @Override public Class getObjectType() { return classToBeMocked; } @Override public boolean isSingleton() { return true; } } 

Затем обновите конфигурацию пружины следующим образом:

       

Рассматривая темпы развития Springockito и количество открытых проблем , я бы немного побеспокоился, чтобы представить его в стеке набора тестов в наши дни. Факт, что последний релиз был сделан до выпуска весны 4, вызывает такие вопросы, как «Можно ли легко интегрировать его с Spring 4?». Я не знаю, потому что я не пробовал. Я предпочитаю использовать чистый весенний подход, если мне нужно высмеять Spring bean в тесте интеграции.

Существует возможность подделать Spring bean с просто функциями Spring. Вам нужно использовать @Primary , @Profile и @ActiveProfiles . Я написал сообщение в блоге по этой теме.

Я нашел аналогичный ответ как teabot для создания MockFactory, который предоставляет mocks. Я использовал следующий пример для создания фабрики макетов (поскольку ссылка на 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

Я использую комбинацию подхода, используемого в ответе Markus T, и простую вспомогательную реализацию ImportBeanDefinitionRegistrar которая ищет пользовательскую аннотацию ( @MockedBeans ), в которой можно указать, какие classы нужно издеваться. Я считаю, что этот подход приводит к краткому модульному тесту с некоторым кодом шаблона, связанным с издевательством.

Вот как выглядит примерный тестовый пример с таким подходом:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(loader=AnnotationConfigContextLoader.class) public class ExampleServiceIntegrationTest { //our service under test, with mocked dependencies injected @Autowired ExampleService exampleService; //we can autowire mocked beans if we need to used them in tests @Autowired DependencyBeanA dependencyBeanA; @Test public void testSomeMethod() { ... exampleService.someMethod(); ... verify(dependencyBeanA, times(1)).someDependencyMethod(); } /** * Inner class configuration object for this test. Spring will read it thanks to * @ContextConfiguration(loader=AnnotationConfigContextLoader.class) annotation on the test class. */ @Configuration @Import(TestAppConfig.class) //TestAppConfig may contain some common integration testing configuration @MockedBeans({DependencyBeanA.class, DependencyBeanB.class, AnotherDependency.class}) //Beans to be mocked static class ContextConfiguration { @Bean public ExampleService exampleService() { return new ExampleService(); //our service under test } } } 

Чтобы это произошло, вам нужно определить два простых вспомогательных classа – пользовательскую аннотацию ( @MockedBeans ) и пользовательскую реализацию ImportBeanDefinitionRegistrar . @MockedBeans annotations @MockedBeans должно быть аннотировано с помощью @Import(CustomImportBeanDefinitionRegistrar.class) а для ImportBeanDefinitionRgistrar необходимо добавить в конфигурацию в своем методе registerBeanDefinitions добавленные определения ImportBeanDefinitionRgistrar .

Если вам нравится этот подход, вы можете найти примеры реализации на моем блоге .

Я разработал решение, основанное на предложении Kresimir Nesek. Я добавил новую аннотацию @EnableMockedBean , чтобы сделать код немного более чистым и модульным.

 @EnableMockedBean @SpringBootApplication @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes=MockedBeanTest.class) public class MockedBeanTest { @MockedBean private HelloWorldService helloWorldService; @Autowired private MiddleComponent middleComponent; @Test public void helloWorldIsCalledOnlyOnce() { middleComponent.getHelloMessage(); // THEN HelloWorldService is called only once verify(helloWorldService, times(1)).getHelloMessage(); } } 

Я написал сообщение, объясняющее это.

Я предлагаю перенести ваш проект на Spring Boot 1.4. После этого вы можете использовать новую аннотацию @MockBean для подделки вашего com.package.Dao

Сегодня я узнал, что весенний контекст, где я объявил перед бобами Mockito, не смог загрузить. После перемещения ПОСЛЕ изнасилований контекст приложения был успешно загружен. Береги себя 🙂

Для записи все мои тесты корректно работают, просто сделав светильник ленивым инициализированным, например:

          

Я полагаю, что рассуждение – это тот, который объясняет Маттиас здесь (внизу сообщения), что обходной путь изменяет порядок, в котором объявляются бобы – ленивая инициализация – это «своего рода», с объявлением, объявленным в конце.

Если вы используете Injection Controller, убедитесь, что ваши локальные переменные не являются «окончательными»,

  • Как избежать исключения «Circular view path» с использованием теста Spring MVC
  • Использование Spring, сопоставление с root в web.xml, статические ресурсы не найдены
  • определить целевой URL на основе ролей в весенней безопасности 3.1
  • Как использовать Spring Security без сеансов?
  • Spring Autowiring class vs. interface?
  • исключить @Component из @ComponentScan
  • получение исключения: не определен bean с именем «springSecurityFilterChain»
  • Можно ли настроить SpringMVC для обработки всех запросов, но исключить каталоги статического содержимого?
  • Самовсасывание с пружиной
  • Spring Boot JPA - настройка автоматического повторного подключения
  • @Value не разрешается при использовании annotations @PropertySource. Как настроить PropertySourcesPlaceholderConfigurer?
  • Давайте будем гением компьютера.