Переопределение Spring beans в среде модульного тестирования

StackOverflow https://stackoverflow.com/questions/565334

Вопрос

мы используем Spring для целей моего приложения и Spring Testing framework для модульных тестов.Однако у нас есть небольшая проблема:код приложения загружает контекст приложения Spring из списка расположений (xml-файлов) в classpath.Но когда мы запускаем наши модульные тесты, мы хотим, чтобы некоторые компоненты Spring были mocks вместо полноценных классов реализации.Более того, для некоторых модульных тестов мы хотим, чтобы некоторые компоненты стали mocks, в то время как для других модульных тестов мы хотим, чтобы другие компоненты стали mocks, поскольку мы тестируем разные уровни приложения.

Все это означает, что я хочу переопределить конкретные компоненты контекста приложения и обновить контекст при желании.Делая это, я хочу переопределить только небольшую часть компонентов, расположенных в одном (или нескольких) исходном файле определения xml-компонентов.Я не могу найти простой способ сделать это.Всегда считалось, что Spring - это фреймворк, удобный для модульного тестирования, так что я, должно быть, здесь чего-то не хватает.

У вас есть какие-нибудь идеи, как это сделать?

Спасибо.

Это было полезно?

Решение

я бы предложил пользовательский тестовый класс и несколько простых правил для расположения источника bean.xml

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath*:spring/*.xml",
    "classpath*:spring/persistence/*.xml",
    "classpath*:spring/mock/*.xml"})
@Transactional
@TestExecutionListeners({
    DependencyInjectionTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class})
public abstract class AbstractHibernateTests implements ApplicationContextAware 
{

    /**
     * Logger for Subclasses.
     */
    protected final Logger LOG = LoggerFactory.getLogger(getClass());

    /**
     * The {@link ApplicationContext} that was injected into this test instance
     * via {@link #setApplicationContext(ApplicationContext)}.
     */
    protected ApplicationContext applicationContext;

    /**
     * Set the {@link ApplicationContext} to be used by this test instance,
     * provided via {@link ApplicationContextAware} semantics.
     */
    @Override
    public final void setApplicationContext(
            final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

если они есть mock-bean.xml в указанном местоположении, они переопределят все "реальные" bean.xml в "обычных" местоположениях - ваши обычные местоположения могут отличаться

но...я бы никогда не стал смешивать макетные и не-макетные компоненты, так как трудно отследить проблемы, когда приложение становится старше.

Другие советы

Одна из причин, по которой spring описывается как удобный для тестирования, заключается в том, что может быть легко просто новое или макет материала в модульном тестировании.

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

Для всех компонентов, которым требуются разные реализации в разных контекстах, переключитесь на подключение на основе аннотаций.Вы можете оставить остальные как есть.

Реализуйте следующий набор аннотаций

 <context:component-scan base-package="com.foobar">
     <context:include-filter type="annotation" expression="com.foobar.annotations.StubRepository"/>
     <context:include-filter type="annotation" expression="com.foobar.annotations.TestScopedComponent"/>
     <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
 </context:component-scan>

Затем вы комментируете свой ЖИВЫЕ КОНЦЕРТЫ реализации с @Repository, ваш заглушка реализации с @StubRepository, любой код, который должен присутствовать в приспособлении для модульного тестирования, ТОЛЬКО с @TestScopedComponent.Возможно, вам понадобится еще пара примечаний, но это отличное начало.

Если у вас много spring.xml, вам, вероятно, потребуется создать несколько новых XML-файлов spring, которые в основном содержат только определения проверки компонентов.Обычно вы просто добавляете эти файлы в свой обычный список @ContextConfiguration.Причина этого заключается в том, что вы часто сталкиваетесь с различными конфигурациями контекстных сканирований (поверьте мне, вы будет сделайте по крайней мере еще 1 аннотацию, если вы проводите веб-тесты, что составляет 4 релевантных комбинации)

Тогда вы в основном используете

@ContextConfiguration(locations = { "classpath:/path/to/root-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)

Обратите внимание, что эта настройка выполняет не позволяет вам иметь чередующиеся комбинации заглушек / текущих данных.Мы попробовали это, и я думаю, что это привело к беспорядку, который я бы никому не рекомендовал ;) Мы либо подключаем inn к полному набору заглушек, либо к полному набору живых сервисов.

В основном мы используем автоматически подключаемые зависимости заглушек при тестировании графического интерфейса рядом с материалами, где зависимости обычно довольно существенны.В более чистых областях кода мы используем более регулярное модульное тестирование.

В нашей системе у нас есть следующие xml-файлы для проверки компонентов:

  • для обычного производства веб-страниц
  • для запуска web только с заглушками
  • для интеграционных тестов (в junit)
  • для модульных тестов (в junit)
  • для веб-тестов selenium (в junit)

Это означает, что у нас всего есть 5 различных общесистемных конфигураций, с которыми мы можем запустить приложение.Поскольку мы используем только аннотации, spring достаточно быстр, чтобы автоматически подключать даже те модульные тесты, которые мы хотим подключить.Я знаю, это нетрадиционно, но это действительно здорово.

Наши интеграционные тесты выполняются с полной настройкой в реальном времени, и один или два раза я решил получить действительно прагматичный и хочет иметь 5 живых проводов и один макет:

public class HybridTest {
   @Autowired
   MyTestSubject myTestSubject;


   @Test
   public void testWith5LiveServicesAndOneMock(){
     MyServiceLive service = myTestSubject.getMyService();
     try {
          MyService mock = EasyMock.create(...)
          myTestSubject.setMyService( mock);

           .. do funky test  with lots of live but one mock object

     } finally {
          myTestSubject.setMyService( service);
     }


   }
}

Я знаю, что специалисты по тестированию набросятся на меня из-за этого.Но иногда это просто очень прагматичное решение, которое оказывается очень элегантным, когда альтернатива была бы действительно очень уродливой.Опять же, обычно это происходит в областях, близких к графическому интерфейсу пользователя.

Видишь это учебное пособие с аннотацией @InjectedMock

Это сэкономило мне уйму времени.Вы просто используете

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

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

и все ваши проблемы будут решены.Mockito заменит внедрение spring dependency на mock .Я только что использовал его сам, и он отлично работает.

Здесь перечислены некоторые очень сложные и мощные решения.

Но есть один НАМНОГО, НАМНОГО проще способ выполнить то, о чем просил Stas, который не включает в себя изменение чего-либо, кроме одной строки кода в методе тестирования.Это работает как для модульных тестов, так и для тестов интеграции Spring, для автоматически подключаемых зависимостей, частных и защищенных полей.

Вот оно:

junitx.util.PrivateAccessor.setField(testSubject, "fieldName", mockObject);

Вы также можете написать свои модульные тесты, чтобы они вообще не требовали никаких поисковых запросов:

@ContextConfiguration(locations = { "classpath:/path/to/test-config.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class MyBeanTest {

    @Autowired
    private MyBean myBean; // the component under test

    @Test
    public void testMyBean() {
        ...
    }
}

Это дает простой способ смешивать и сопоставлять реальные конфигурационные файлы с тестовыми конфигурационными файлами.

Например, при использовании hibernate у меня может быть компонент SessionFactory в одном файле конфигурации (который будет использоваться как в тестах, так и в основном приложении), а компонент by DataSource - в другом файле конфигурации (один может использовать DriverManagerDataSource для базы данных в памяти, другой может использовать JNDI-lookup).

Но, определенно, примите во внимание @клетуса предупреждение ;-)

Легко.Вы используете пользовательский контекст приложения для своих модульных тестов.Или вы вообще его не используете, а вручную создаете и внедряете свои компоненты.

Мне кажется, что ваше тестирование может быть слишком широким.Модульное тестирование - это тестирование, ну, единиц измерения.Весенний боб - довольно хороший пример единицы измерения.Для этого вам не должен нужен весь контекст приложения.Я нахожу, что если ваше модульное тестирование настолько высокого уровня, что вам нужны сотни компонентов, подключений к базе данных и т.д., То у вас действительно хрупкий модульный тест, который сломается при следующем изменении, будет трудно поддерживать и на самом деле не принесет большой пользы.

Вы можете использовать импорт включите функцию в контексте вашего тестового приложения для загрузки в компоненты prod и переопределите те, которые вы хотите.Например, мой источник данных prod обычно получается через JNDI lookup, но при тестировании я использую источник данных DriverManager, поэтому мне не нужно запускать сервер приложений для тестирования.

У меня нет очков репутации, чтобы наваливать на ответ даффимо, но я просто хотел вмешаться и сказать, что его ответ был "правильным" для меня.

Создайте экземпляр FileSystemXmlApplicationContext в настройках вашего модульного теста с помощью пользовательского applicationContext.xml.В этом пользовательском xml-файле вверху выполните, как указывает duffymo.Затем объявите свои фиктивные компоненты, источники данных, отличные от JNDI, и т.д., Которые переопределят идентификаторы, объявленные при импорте.

Для меня это сработало как мечта.

Вам не нужно использовать какие-либо тестовые контексты (не имеет значения, на основе XML или Java).Начиная с Spring boot 1.4, доступна новая аннотация @MockBean который ввел встроенную поддержку для издевательства и слежки за Spring Beans.

Возможно, вы могли бы использовать квалификаторы для своих бобов?Вы бы переопределили компоненты, которые хотите создать в отдельном контексте приложения, и пометили бы их определителем "test".В ваших модульных тестах при подключении ваших компонентов всегда указывайте квалификатор "test" для использования макетов.

Я хочу сделать то же самое, и мы считаем это необходимым.

Нынешний механизм, который мы используем, довольно ручной, но он работает.

Скажем, например, вы хотите смоделировать bean типа Y.Что мы делаем, так это каждый компонент, имеющий эту зависимость, который мы заставляем реализовывать интерфейс - "IHasY".Этот интерфейс является

interface IHasY {
   public void setY(Y y);
}

Затем в нашем тесте мы вызываем метод util...

 public static void insertMock(Y y) {
        Map invokers = BeanFactory.getInstance().getFactory("core").getBeansOfType(IHasY.class);
        for (Iterator iterator = invokers.values().iterator(); iterator.hasNext();) {
            IHasY invoker = (IHasY) iterator.next();
            invoker.setY(y);
        }
    }

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

Если вы готовы создать конфигурационный файл xml, то можно было бы создать новую фабрику с макетными компонентами и сделать вашу фабрику по умолчанию родительской для этой фабрики.Затем убедитесь, что вы загрузили все свои компоненты с новой дочерней фабрики.При выполнении этого дочерняя фабрика переопределит компоненты на родительской фабрике, если идентификаторы компонента совпадают.

Теперь, если бы в моем тесте, если бы я мог программно создать фабрику, это было бы потрясающе.Необходимость использовать xml просто слишком громоздка.Я хочу создать эту дочернюю фабрику с помощью кода.Затем каждый тест может настроить свою фабрику так, как он хочет.Нет никаких причин, по которым такая фабрика не будет работать.

пружинный ввод предназначен для замены фасоли маком.

После операции появилось вот это: Спрингокито

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top