Повторное использование контекста приложения Spring в тестовых классах junit

84

У нас есть несколько тестовых примеров JUnit (интеграционные тесты), и они логически сгруппированы в разные тестовые классы.

Мы можем загружать контекст приложения Spring один раз для каждого тестового класса и повторно использовать его для всех тестовых случаев в тестовом классе JUnit, как указано в http://static.springsource.org/spring/docs/current/spring-framework-reference /html/testing.html

Однако нам просто интересно, есть ли способ загрузить контекст приложения Spring только один раз для группы тестовых классов JUnit.

FWIW, мы используем Spring 3.0.5, JUnit 4.5 и Maven для сборки проекта.

Рамеш
источник
5
Все ответы ниже прекрасны, но у меня нет context.xml. Примечал ли я свой путь в небытие? Любой способ сделать это без context.xml?
markthegrea
2
ты нашел ответ на свое решение? У меня такая же проблема, и я хочу сделать это с помощью аннотаций и Spring Boot.
AleksandarT

Ответы:

96

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

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring кэширует контексты приложения по locationsатрибуту, поэтому, если он locationsпоявляется во второй раз, Spring использует тот же контекст, а не создает новый.

Я написал статью об этой функции: Ускорение интеграционных тестов Spring . Также это подробно описано в документации Spring: 9.3.2.1 Управление контекстом и кеширование .

Это имеет интересный смысл. Поскольку Spring не знает, когда JUnit завершен, он навсегда кэширует весь контекст и закрывает их с помощью обработчика выключения JVM. Такое поведение (особенно когда у вас много тестовых классов с разными locations) может привести к чрезмерному использованию памяти, утечкам памяти и т. Д. Еще одно преимущество кеширования контекста.

Томаш Нуркевич
источник
Ах! Не осознавал этого. Мы придерживаемся этого подхода в течение долгого времени, и я (ошибочно) приписал долгую продолжительность выполнения теста загрузке контекста Spring с каждым тестовым классом. Сейчас внимательно проверю. Благодарю.
Рамеш
1
Я бы сказал, что Spring ничего не знает о порядке выполнения ваших тестов. В результате этого он не может определить, потребуется ли контекст позже или его можно будет удалить.
philnate
1
Я не понимаю, как это может быть правдой. Eclipse / JUnit тратит 2 минуты на запуск среды каждый раз, когда я выполняю тест Run As / JUnit. Этого бы не произошло, если бы что-нибудь было кэшировано.
user1944491 02
3
Есть идеи, можно ли это сделать полностью с помощью аннотаций вместо использования XML для определения контекста? Я много искал об этом в документе и здесь, на SO, но не смог найти ничего, что заставляет меня думать, что это невозможно.
Жан-Франсуа Савар,
это не так, если у вас есть инициализаторы? мой инициализируется для каждого теста в классе
Калпеш Сони
26

Чтобы добавить к ответу Томаша Нуркевича , с Spring 3.2.2 @ContextHierarchyаннотацию можно использовать для создания отдельной связанной множественной контекстной структуры. Это полезно, когда несколько тестовых классов хотят совместно использовать (например) настройки базы данных в памяти (источник данных, EntityManagerFactory, tx manager и т. Д.).

Например:

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("FirstTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class FirstTest {
 ...
}

@ContextHierarchy({
  @ContextConfiguration("/test-db-setup-context.xml"),
  @ContextConfiguration("SecondTest-context.xml")
})
@RunWith(SpringJUnit4ClassRunner.class)
public class SecondTest {
 ...
}

При такой настройке контекст, который использует "test-db-setup-context.xml", будет создан только один раз, но bean-компоненты внутри него могут быть введены в контекст отдельного модульного теста.

Подробнее о руководстве: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/testing.html#testcontext-ctx-management (поиск по запросу « иерархия контекста »)

Gerrytan
источник
У меня многомодульный maven, и я стараюсь избегать настройки базы данных в служебном модуле (поскольку он уже загружен тестами модуля доступа к данным), и у меня это не работает!
Мухаммад Хьюеди
5
Это сработало для меня! Благодарю. Чтобы быть ясным, без аннотации @ContextHierarchy spring загружает мой db для каждого теста. Я использую "классы" параметров: @ContextConfiguration (классы = {JpaConfigTest.class, ...
Брель
5
Есть идеи, можно ли это сделать полностью с помощью аннотаций вместо использования XML для определения контекста? Я много искал об этом в документе и здесь, на SO, но не смог найти ничего, что заставляет меня думать, что это невозможно.
Жан-Франсуа Савар,
1
@ Jean-FrançoisSavard Вам повезло с поиском (через annotationsw вместо XML)?
javadev
@javadev Я надеюсь, что это именно то, что вы ищете docs.spring.io/spring/docs/current/spring-framework-reference/…
Равитея Губба
1

По сути, Spring достаточно умен, чтобы настроить это за вас, если у вас одинаковая конфигурация контекста приложения для разных тестовых классов. Например, допустим, у вас есть два класса A и B следующим образом:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

В этом примере класс A имитирует bean-компонент C, тогда как класс B имитирует bean-компонент D. Таким образом, Spring рассматривает их как две разные конфигурации и, таким образом, загружает контекст приложения один раз для класса A и один раз для класса B.

Если бы вместо этого мы хотели, чтобы Spring разделял контекст приложения между этими двумя классами, они должны были бы выглядеть следующим образом:

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class A {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

@ActiveProfiles("h2")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class B {

    @MockBean
    private C c;

    @MockBean
    private D d;
    //Autowired fields, test cases etc...
}

Если вы подключите свои классы таким образом, spring загрузит контекст приложения только один раз либо для класса A, либо для класса B, в зависимости от того, какой из двух классов запускается первым в наборе тестов. Это может быть воспроизведено в нескольких тестовых классах, единственное условие состоит в том, что вы не должны настраивать тестовые классы по-другому. Любая настройка, которая приводит к тому, что тестовый класс будет отличаться от другого (в глазах весны), к весне приведет к созданию другого контекста приложения.

Саураб Гур
источник
0

создайте свой класс конфигурации, как показано ниже

@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class )
@SpringBootTest(classes ={add your spring beans configuration classess})
@TestPropertySource(properties = {"spring.config.location=classpath:application"})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
public class RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(S2BXISINServiceTest.class);


    //auto wire all the beans you wanted to use in your test classes
    @Autowired
    public XYZ xyz;
    @Autowired
    public ABC abc;


    }



Create your test suite like below



@RunWith(Suite.class)
@Suite.SuiteClasses({Test1.class,test2.class})
public class TestSuite extends RunConfigration {

    private ClassLoader classloader = Thread.currentThread().getContextClassLoader();

    private static final Logger LOG = LoggerFactory.getLogger(TestSuite.class);


}

Создайте свои тестовые классы, как показано ниже

public class Test1 extends RunConfigration {


  @Test
    public void test1()
    {
    you can use autowired beans of RunConfigration classes here 
    }

}


public class Test2a extends RunConfigration {

     @Test
    public void test2()
    {
    you can use autowired beans of RunConfigration classes here 
    }


}
Дилип Гудла
источник
0

Один замечательный момент заключается в том, что если мы используем @SpringBootTests, но снова use @MockBean in different test classes , Spring не имеет возможности повторно использовать контекст своего приложения для всех тестов.

Решение есть, to move all @MockBean into an common abstract classи это устраняет проблему.

@SpringBootTests(webEnvironment = WebEnvironment.RANDOM_PORT, classes = Application.class)
public abstract class AbstractIT {

   @MockBean
   private ProductService productService;

   @MockBean
   private InvoiceService invoiceService;

}

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

public class ProductControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchProduct_ShouldSuccess() {
   }

}

public class InvoiceControllerIT extends AbstractIT {
   // please don't use @MockBean here
   @Test
   public void searchInvoice_ShouldSuccess() {
   }

}
Куок Чыонг
источник