Инъекция Мокито издевается над весенним бобаном
Я хотел бы добавить объект Mockito mock в компонент Spring (3+) для целей модульного тестирования с помощью JUnit. В настоящее время @Autowired
зависимости вводятся с помощью annotations @Autowired
в частных полях-членах.
Я рассмотрел использование ReflectionTestUtils.setField
но экземпляр компонента, который я хотел бы вставить, на самом деле является прокси-сервером и, следовательно, не объявляет поля частного члена целевого classа. Я не хочу создавать публичный сеттер для зависимости, поскольку я буду модифицировать свой интерфейс только для целей тестирования.
Я следил за советами, предоставленными сообществом Spring, но макет не создается и автоматическая проводка выходит из строя:
- Как регистрировать HttpRequest и HttpResponse в файле?
- Каковы преимущества контейнеров для инъекций зависимостей?
- Невозможно Autowire @Repository аннотированный интерфейс в Spring Boot
- Весенние данные и исходный запрос с разбивкой на страницы
- Данные Spring: поддерживается «delete by»?
Ошибка, с которой я сталкиваюсь, выглядит следующим образом:
... 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
на что-то недействительное, при запуске контекста приложения не возникает ошибки.
- Springboot / Angular2 - Как обрабатывать URL-адреса HTML5?
- Как установить заголовок «Принять:» в запросе Spring RestTemplate?
- Безопасность Springboot не работает
- Исправления для проверки подлинности безопасности пружины с помощью @ExceptionHandler
- Spring + Web MVC: dispatcher-servlet.xml и applicationContext.xml (плюс общая безопасность)
- Setter DI или Constructor DI весной?
- Как получить объект сеанса весной?
- Загрузка файлов с помощью API-интерфейса Angular2 в REST
Лучший способ:
Обновить
В контекстном файле этот макет должен быть указан до того, как будет объявлено какое-либо поле с автоопределением в зависимости от его объявления.
@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, убедитесь, что ваши локальные переменные не являются «окончательными»,