6.7.2. Интеграционные тесты Middleware

На уровне Middleware можно создавать интеграционные тесты, которые выполняются в полнофункциональном контейнере Spring с подключением к базе данных. В тестах такого типа можно выполнять код любого слоя внутри Middleware - от сервисов до ORM.

Для того, чтобы выполнять тесты из IDE, создайте каталог test в модуле core рядом с src. После этого пересоздайте проектные файлы IDE.

Платформа содержит класс TestContainer, который может быть использован в качестве базового для тестовых контейнеров приложения. Создайте наследника этого класса в каталоге test модуля core и в его конструкторе переопределите параметры загрузки компонентов и свойств приложения, а также параметры подключения к тестовой БД. Например:

public class SalesTestContainer extends TestContainer {

    public SalesTestContainer() {
        super();
        appComponents = new ArrayList<>(Arrays.asList(
                "com.haulmont.cuba"
                // add CUBA premium add-ons here
                // "com.haulmont.bpm",
                // "com.haulmont.charts",
                // "com.haulmont.fts",
                // "com.haulmont.reports",
                // and custom app components if any
        ));
        appPropertiesFiles = Arrays.asList(
                // List the files defined in your web.xml
                // in appPropertiesConfig context parameter of the core module
                "sales-app.properties",
                // Add this file which is located in CUBA and defines some properties
                // specifically for test environment. You can replace it with your own
                // or add another one in the end.
                "test-app.properties");
        dbDriver = "org.postgresql.Driver";
        dbUrl = "jdbc:postgresql://localhost/sales_test";
        dbUser = "cuba";
        dbPassword = "cuba";
    }
}

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

configure(coreModule) {
...
    task createTestDb(dependsOn: assemble, description: 'Creates local Postgres database for tests', type: CubaDbCreation) {
        dbms = 'postgres'
        dbName = 'sales_test'
        dbUser = 'cuba'
        dbPassword = 'cuba'
    }

Тестовый контейнер используется в классах тестов в качестве JUnit rule, указанного с помощью аннотации @ClassRule:

public class CustomerLoadTest {

    @ClassRule
    public static SalesTestContainer cont = new SalesTestContainer();

    private Customer customer;

    @Before
    public void setUp() throws Exception {
        customer = cont.persistence().createTransaction().execute(em -> {
            Customer customer = new Customer();
            customer.setName("testCustomer");
            em.persist(customer);
            return customer;
        });
    }

    @After
    public void tearDown() throws Exception {
        cont.deleteRecord(customer);
    }

    @Test
    public void test() {
        try (Transaction tx = cont.persistence().createTransaction()) {
            EntityManager em = cont.persistence().getEntityManager();
            TypedQuery<Customer> query = em.createQuery(
                "select c from sales$Customer c", Customer.class);
            List<Customer> list = query.getResultList();
            tx.commit();
            assertTrue(list.size() > 0);
        }
    }
}

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

Так как запуск контейнера занимает некоторое время, имеет смысл инициализировать контейнер один раз для тестов из нескольких (или всех) классов. Для этого создайте общий синглтон-экземпляр тестового контейнера, например:

public class SalesTestContainer extends TestContainer {

    public SalesTestContainer() {
        ...
    }

    public static class Common extends SalesTestContainer {

        public static final SalesTestContainer.Common INSTANCE = new SalesTestContainer.Common();

        private static volatile boolean initialized;

        private Common() {
        }

        @Override
        public void before() throws Throwable {
            if (!initialized) {
                super.before();
                initialized = true;
            }
            setupContext();
        }

        @Override
        public void after() {
            cleanupContext();
            // never stops - do not call super
        }
    }
}

И используйте его в тестовых классах:

public class CustomerLoadTest {

    @ClassRule
    public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;

    ...
}
Полезные методы тестового контейнера

Класс TestContainer содержит следующие методы, которые можно использовать в коде тестов (см. пример CustomerLoadTest выше):

  • persistence() - возвращает ссылку на интерфейс Persistence.

  • metadata() - возвращает ссылку на интерфейс Metadata.

  • deleteRecord() - этот набор перегруженных методов предназначен для использования в @After методах для удаления тестовых объектов из БД.

Логгирование

Класс TestContainer настраивает логгирование в соответствие с файлом test-logback.xml, предоставляемым платформой. Данный файл содержится в артефакте cuba-core-tests.

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

  • Скопируйте файл test-logback.xml из артефакта платформы в корень каталога test модуля core проекта, например как my-test-logback.xml.

  • Сконфигурируйте параметры логгирования в my-test-logback.xml.

  • Добавьте блок статической инициализации в класс тестового контейнера проекта и укажите местоположение файла конфигурации Logback в системном свойстве logback.configurationFile:

    public class MyTestContainer extends TestContainer {
    
        static {
            System.setProperty("logback.configurationFile", "my-test-logback.xml");
        }
    
        // ...
    }
Дополнительные хранилища

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

public class MyTestContainer extends TestContainer {
    // ...

    @Override
    protected void initDataSources() {
        super.initDataSources();
        try {
            Class.forName("org.postgresql.Driver");
            TestDataSource mydbDataSource = new TestDataSource("jdbc:postgresql://localhost/mydatabase", "cuba", "cuba");
            TestContext.getInstance().bind(AppContext.getProperty("cuba.dataSourceJndiName_mydb"), mydbDataSource);
        } catch (ClassNotFoundException | NamingException e) {
            throw new RuntimeException("Error initializing datasource", e);
        }
    }

    // ...
}