Инъекция 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 ]

125 голосов
/ 18 августа 2010

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

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

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

106 голосов
/ 05 января 2012
@InjectMocks
private MyTestObject testObject;

@Mock
private MyDependentObject mockedObject;

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

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

62 голосов
/ 20 августа 2012

У меня очень простое решение с использованием 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;
    }
}
41 голосов
/ 28 марта 2014

Дано:

@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 делает по умолчанию сейчас)

34 голосов
/ 08 октября 2015

Если вы используете 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 или используете предыдущую версию, вам придется проделать немного больше работы:

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

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

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

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

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

Наконец, в своем тесте активируйте 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() {
        ....
    }
}
19 голосов
/ 07 марта 2011

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

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

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

13 голосов
/ 17 марта 2010

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

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

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

Теперь моё определение бобов выглядит так:

<bean id="mockDaoFactory" name="dao" class="com.package.test.MocksFactory">
    <property name="type" value="com.package.Dao" />
</bean>
11 голосов
/ 18 октября 2013

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

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

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

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

<bean id="dao" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.package.Dao" />
</bean>
9 голосов
/ 28 сентября 2011

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

<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> 
9 голосов
/ 20 июля 2011

Если вы используете 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, это можно сделать и наоборот:

<beans>
    <!-- rest of your config -->

    <!-- the container recognize this as a Configuration and adds it's beans 
         to the container -->
    <bean class="com.package.DaoTestConfiguration"/>
</beans>

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

...