Повторно использовать контекст приложения 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 
    }


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