Повторно использовать контекст приложения Spring в тестовых классах Junit
У нас есть несколько тестовых примеров 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 для создания проекта.
2 ответа
Да, это вполне возможно. Все, что вам нужно сделать, это использовать то же самое locations
атрибут в ваших тестовых классах:
@ContextConfiguration(locations = "classpath:test-context.xml")
Spring кэширует контексты приложения locations
атрибут, так что если тот же locations
появляется во второй раз, Spring использует тот же контекст, а не создает новый.
Я написал статью об этой функции: ускорение интеграционных тестов Spring. Также это подробно описано в документации Spring: 9.3.2.1 Управление контекстом и кэширование.
Это имеет интересный смысл. Поскольку Spring не знает, когда JUnit завершен, он кэширует весь контекст навсегда и закрывает их с помощью ловушки завершения работы JVM. Такое поведение (особенно когда у вас много тестовых классов с разными locations
) может привести к чрезмерному использованию памяти, утечкам памяти и т. д. Еще одно преимущество контекста кэширования.
Добавить к ответу Томаша Нуркевича, начиная с весны 3.2.2 @ContextHierarchy
аннотация может использоваться, чтобы иметь отдельную, связанную многоконтекстную структуру. Это полезно, когда несколько тестовых классов хотят совместно использовать (например) настройки базы данных в памяти (источник данных, EntityManagerFactory, менеджер tx и т. Д.).
Например:
@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 (поиск по " иерархии контекста")
Один замечательный момент заключается в том, что если мы используем @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() {
}
}
По сути, 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 в зависимости от того, какой класс из двух запускается первым в наборе тестов. Это может быть воспроизведено в нескольких тестовых классах, единственное условие состоит в том, что вы не должны настраивать тестовые классы по-разному. Любая настройка, которая приводит к тому, что тестовый класс будет отличаться от другого (в глазах весны), к весне приведет к созданию другого контекста приложения.
создайте свой класс конфигурации, как показано ниже
@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
}
}