Переопределение бобов Spring в среде модульных тестов

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

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

У вас есть идеи, как это сделать?

Благодарю.

13 ответов

Решение

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

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

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

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

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

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

 <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-файлов, которые в основном содержат только определения компонентного сканирования. Обычно вы просто добавляете эти файлы в свой обычный список @ContextConfiguration. Причина этого в том, что вы часто сталкиваетесь с различными конфигурациями сканирования контекста (поверьте мне, вы сделаете как минимум еще 1 аннотацию, если будете проводить веб-тесты, то есть для 4 соответствующих комбинаций)

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

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

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

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

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

  • для регулярного веб-производства
  • для запуска веб только с заглушками
  • для интеграционных тестов (в junit)
  • для юнит-тестов (в юнитах)
  • для веб-тестов селена (в 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);
     }


   }
}

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

Смотрите это руководство с аннотацией @InjectedMock

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

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

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

и все твои проблемы решены. Mockito заменит пружинный впрыск зависимостей на макет. Я просто использовал его сам, и он прекрасно работает.

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

Но есть FAR, FAR более простой способ выполнить то, о чем просил Стас, который не включает в себя изменение чего-либо, кроме одной строки кода в тестовом методе. Он работает как для модульных тестов, так и для тестов интеграции 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 у меня может быть bean-компонент sessionFactory в одном файле конфигурации (который будет использоваться как в тестах, так и в главном приложении), а bean-компонентом dataSource - в другом файле конфигурации (можно использовать DriverManagerDataSource для память базы данных, другой может использовать поиск JNDI).

Но обязательно прислушайтесь к предупреждению @cletus;-)

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

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

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

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

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

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

Работал как мечта для меня.

Spring-reinject предназначен для замены бобов с насмешками.

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

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

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

Скажем, например, вы хотите смоделировать bean-компонент типа Y. То, что мы делаем, - это каждый bean-компонент, имеющий такую ​​зависимость, которую мы создаем для реализации интерфейса - "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, тогда вам нужно будет создать новую фабрику с фиктивными компонентами и сделать фабрику по умолчанию родителем этой фабрики. Убедитесь, что вы загрузили все ваши бины с новой дочерней фабрики. При этом суб-фабрика переопределит bean-компоненты в родительской фабрике, когда идентификаторы bean-идентификаторов совпадают.

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

Начиная с OP это пришло: Springockito

Другие вопросы по тегам