Injecting Mockito издевается над Spring bean

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

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

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

Ответ 1

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

<bean id="dao" class="org.mockito.Mockito" factory-method="mock"> 
    <constructor-arg value="com.package.Dao" /> 
</bean> 

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

Ответ 2

@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

@Before
public void setup() {
        MockitoAnnotations.initMocks(this);
}

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

Ответ 3

У меня очень простое решение, использующее 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;
    }
}

Ответ 4

Дано:

@Service
public class MyService {
    @Autowired
    private MyDAO myDAO;

    // etc
}

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

@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
}

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

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

Ответ 5

Если вы используете Spring Boot 1.4, у него есть отличный способ сделать это. Просто используйте новый бренд @SpringBootTest для своего класса и @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 bean, который вводит ваши mocks в контекст Spring:

@Configuration
@Profile("useMocks")
public class MockConfigurer {

    @Bean
    @Primary
    public MyBean myBeanSpy() {
        return mock(MyBean.class);
    }
}

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

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

Наконец, в вашем тесте активируйте профиль 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() {
        ....
    }
}

Если вы не хотите использовать макет, но реальный bean, просто не активируйте профиль 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() {
        ....
    }
}

Ответ 6

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

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

Отмечу, что он, похоже, не может вводить List<T> так же, как Spring. Он ищет только объект Mock, который удовлетворяет List, и не вводит список объектов Mock. Обходной путь для меня состоял в том, чтобы использовать @Spy против созданного вручную списка и вручную добавить фиктивные объекты в этот список для модульного тестирования. Может быть, это было намеренно, потому что это, безусловно, заставило меня обратить пристальное внимание на то, что насмехалось вместе.

Ответ 7

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

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

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

Мое определение bean теперь выглядит следующим образом:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

Ответ 8

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

Ключевой момент:

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

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

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>

Ответ 9

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

<Предварительно > <код > @Configuration @ImportResource ( "ком/бла/blurk/отдых-оф-config.xml" ) открытый класс DaoTestConfiguration {   @Bean   public ApplicationService applicationService() {       return mock (ApplicationService.class);   } } Код >

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

<Предварительно > <код > < бобы >   <! - остаток вашей конфигурации →   <! - контейнер распознает это как конфигурацию и добавляет ее в beans        к контейнеру →   < bean class= "com.package.DaoTestConfiguration" / > & Л; /& бобы GT; Код >

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

Ответ 10

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

<bean id="dao" class="org.springframework.aop.framework.ProxyFactoryBean">
   <property name="target"> <bean class="org.mockito.Mockito" factory-method="mock"> <constructor-arg value="com.package.Dao" /> </bean> </property>
   <property name="proxyInterfaces"> <value>com.package.Dao</value> </property>
</bean> 

Ответ 11

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

Ответ 12

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

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>

Ответ 13

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

С Spring:

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

Без Spring:

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

Ответ 14

Обновить - новый ответ здесь: fooobar.com/questions/29056/.... Этот ответ применим только к версиям Spring до 3.2.

Я искал какое-то время для более окончательного решения этого. Это сообщение в блоге, похоже, охватывает все мои потребности и не зависит от порядка объявлений bean. Весь кредит Маттиасу Севсерсу. 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<T> implements FactoryBean<T> {

    private Class<T> classToBeMocked;

    /**
     * Creates a Mockito mock instance of the provided class.
     * @param classToBeMocked The class to be mocked.
     */
    public MockitoFactoryBean(Class<T> 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;
    }
}

Далее обновите конфигурацию Spring со следующими параметрами:

<beans...>
    <context:component-scan base-package="com.jayway.example"/>

    <bean id="someDependencyMock" class="com.jayway.springmock.MockitoFactoryBean">
        <constructor-arg name="classToBeMocked" value="com.jayway.example.SomeDependency" />
    </bean>
</beans>

Ответ 15

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

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

Ответ 16

Я нашел аналогичный ответ как teabot для создания MockFactory, который предоставляет mocks. Я использовал следующий пример для создания mock factory (так как ссылка на narkisr мертва): http://hg.randompage.org/java/src/407e78aa08a0/projects/bookmarking/backend/spring/src/test/java/org/randompage/bookmarking/backend/testUtils/MocksFactory.java

<bean id="someFacade" class="nl.package.test.MockFactory">
    <property name="type" value="nl.package.someFacade"/>
</bean>

Это также помогает предотвратить, что Spring хочет разрешить инъекции из mocked bean.

Ответ 17

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

this ^ отлично работает, если объявлено первым/ранним в файле XML. Mockito 1.9.0/ Spring 3.0.5

Ответ 18

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

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

@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
        }
    }
}

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

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

Ответ 19

Я разработал решение, основанное на предложении 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();
    }

}

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

Ответ 20

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

Ответ 21

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

Ответ 22

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

<bean id="fixture"
      class="it.tidalwave.northernwind.rca.embeddedserver.impl.DefaultEmbeddedServer"
      lazy-init="true" /> <!-- To solve Mockito + Spring problems -->

<bean class="it.tidalwave.messagebus.aspect.spring.MessageBusAdapterFactory" />

<bean id="applicationMessageBus"
      class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="it.tidalwave.messagebus.MessageBus" />
</bean>

<bean class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="javax.servlet.ServletContext" />
</bean>

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

Ответ 23

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