Подтвердить что ты не робот

Spring beans переопределение в среде unit test

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

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

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

Спасибо.

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Затем вы в основном используете

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

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

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

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

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


   }
}

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

Ответ 3

Смотрите учебник с аннотацией @InjectedMock

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

@Mock
SomeClass mockedSomeClass

@InjectMock
ClassUsingSomeClass service

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

и все ваши проблемы решены. Mockito заменит инъекцию зависимостей spring макетом. Я просто использовал его сам, и он отлично работает.

Ответ 4

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

Но есть способ FAR, FAR более простой, чтобы выполнить то, что задала Стас, что не требует модификации всего лишь одной строки кода в методе тестирования. Он работает для модульных тестов и Spring интеграционных тестов, для автономных зависимостей, частных и защищенных полей.

Вот он:

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

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

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

Затем в нашем тесте мы вызываем метод утилиты...

 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, тогда вам будет нужно создать новый factory с помощью mock beans и сделать ваш по умолчанию factory родителем этого factory. Убедитесь, что вы загрузили весь свой beans из нового дочернего элемента factory. При этом под factory будет переопределять beans в родительском factory, когда id bean совпадают.

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

Ответ 12

spring-reinject предназначен для замены beans на mocks.

Ответ 13

Поскольку OP это пришло: Springockito