Инъекция Mockito издевается в бобе Spring - PullRequest
265 голосов
/ 16 марта 2010

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

Ответы [ 23 ]

7 голосов
/ 26 апреля 2010

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

<bean id="stateMachine" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.abcd.StateMachine"/>
</bean>
7 голосов
/ 16 марта 2010

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

6 голосов
/ 10 сентября 2012

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

с пружиной:

@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;
}
2 голосов
/ 07 декабря 2014

Глядя на Темпы развития Springockito и количество открытых вопросов , я бы немного волновался о том, чтобы внедрить его в свой набор тестовых наборов в настоящее время. Тот факт, что последний выпуск был сделан до релиза Spring 4, поднимает такие вопросы, как «Возможно ли легко интегрировать его с Spring 4?». Я не знаю, потому что я не пробовал это. Я предпочитаю чистый подход Spring, если мне нужно смоделировать Spring bean в интеграционном тесте.

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

2 голосов
/ 10 июля 2013

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

Затем обновите ваш весенний конфиг следующим образом:

<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>
1 голос
/ 05 сентября 2016

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

1 голос
/ 06 июня 2015

Я разработал решение, основанное на предложении Кресимира Несека. Я добавил новую аннотацию @ 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();
    }

}

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

1 голос
/ 06 июня 2015

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

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

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

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

1 голос
/ 29 февраля 2012

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

1 голос
/ 23 декабря 2012
<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>

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

...