4.8.1. 中间件集成测试

中间件继承测试运行在具有完整功能的 Spring 容器里,而且可以连接数据库。在这些测试类里面,可以运行中间件里面各细分层的代码,比如从 ORM 层到 Service 层。

为了在测试中配置和启动中间件 Spring 容器,需要在项目中创建 com.haulmont.cuba.testsupport.TestContainer 的子类,并且在测试用例中使用其实例作为 JUnit Rule。

下面是容器类和快速开始中提到的 Sales 项目的一个集成测试的示例。所有的类必须在 core 模块的 test 目录。

package com.company.sales;

import com.haulmont.cuba.testsupport.TestContainer;

import java.util.ArrayList;
import java.util.Arrays;

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
                "com/company/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.
                "com/haulmont/cuba/testsupport/test-app.properties");
        initDbProperties();
    }

    private void initDbProperties() {
        dbDriver = "org.postgresql.Driver";
        dbUrl = "jdbc:postgresql://localhost/sales_test";
        dbUser = "cuba";
        dbPassword = "cuba";
    }

    public static class Common extends SalesTestContainer {

        // A common singleton instance of the test container which is initialized once for all tests
        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
        }
    }
}

自定义 test-app.properties 文件的示例:

cuba.webContextName = app-core
sales.someProperty = someValue

推荐使用单独的测试数据库,可以通过 build.gradle 里面定义的 createDb 任务来创建:

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

这个测试容器应当在测试类里面作为 @ClassRule 注解指定的 JUnit 规则(rule):

package com.company.sales;

import com.company.sales.entity.Customer;
import com.haulmont.cuba.core.global.*;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class CustomerTest {

    // Using the common singleton instance of the test container which is initialized once for all tests
    @ClassRule
    public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;

    private Metadata metadata;

    @Before
    public void setUp() throws Exception {
        metadata = cont.metadata();
    }

    @Test
    public void testCreateCustomer() throws Exception {
        // Get a managed bean (or service) from container
        DataManager dataManager = AppBeans.get(DataManager.class);

        // Create new Customer
        Customer customer = metadata.create(Customer.class);
        customer.setName("Test customer");

        // Save the customer to the database
        dataManager.commit(customer);

        // Load the customer by ID
        Customer loaded = dataManager.load(
                LoadContext.create(Customer.class).setId(customer.getId()).setView(View.LOCAL));

        assertNotNull(loaded);
        assertEquals(customer.getName(), loaded.getName());

        // Remove the customer
        dataManager.remove(loaded);
    }
}
几个有用的测试容器方法

TestContainer 类包含了以下几个方法,可以在测试类里面使用(参考上面的 CustomerLoadTest 例子):

  • persistence() – 返回 Persistence 接口的引用。

  • metadata() – 返回 Metadata 接口的引用。

  • deleteRecord() – 这一组重载方法的目的是在 @After 方法里面使用,在测试完成后清理数据库。

日志

测试容器根据平台提供的 test-logback.xml 文件来配置日志。这个文件在 cuba-core-tests 工件的根目录。

可以通过以下方法配置测试的日志级别:

  • 从平台的包里面拷贝 test-logback.xml 到项目 core 模块 test 根目录下,比如可以重命名为 my-test-logback.xml

  • my-test-logback.xml 里面配置 appenders 和 loggers。

  • 在测试容器里面添加一段静态初始化代码,这段代码通过设置 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", "db_user", "db_password");
            TestContext.getInstance().bind(
                    AppContext.getProperty("cuba.dataSourceJndiName_mydb"), mydbDataSource);
        } catch (ClassNotFoundException | NamingException e) {
            throw new RuntimeException("Error initializing datasource", e);
        }
    }
}

还有,如果额外的数据库类型跟主数据库不一致,需要在 build.gradlecore 模块将数据库的驱动添加到 testRuntime 依赖中。示例:

configure(coreModule) {
    // ...
    dependencies {
        // ...
        testRuntime(hsql)
        jdbc('org.postgresql:postgresql:9.4.1212')
        testRuntime('org.postgresql:postgresql:9.4.1212') // add this
    }