6.7.2. Middleware Integration Tests
In the middle tier, you can create integration tests which run in a fully functional Spring container connected to the database. In such tests you can run code from any layer of the Middleware, from services to ORM.
First, create the test
directory in your core module next to the src
directory. Re-create IDE project files to be able to run tests from the IDE.
The platform contains the TestContainer
class which can be used as a base class for the test container in the application project. Create a subclass in the test
directory of your core module and, in its constructor, redefine parameters for loading components and application properties and test database connection parameters. For example:
public class SalesTestContainer extends TestContainer {
public SalesTestContainer() {
super();
appComponents = 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.
"test-app.properties",
"com/company/sales/test-app.properties");
dbDriver = "org.postgresql.Driver";
dbUrl = "jdbc:postgresql://localhost/sales_test";
dbUser = "cuba";
dbPassword = "cuba";
}
}
An example of the custom test-app.properties
file:
cuba.webContextName = app-core
sales.someProperty = someValue
We recommend using a separate test database, which can be created, for example, by the following Gradle task defined in 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'
}
The test container should be used in test classes as a JUnit rule specified by the @ClassRule
annotation:
public class CustomerLoadTest {
@ClassRule
public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
private Customer customer;
@Before
public void setUp() throws Exception {
customer = cont.persistence().createTransaction().execute(em -> {
Customer customer = cont.metadata().create(Customer.class);
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);
}
}
}
In the example above, the test container is initialized once for all test methods of this class, and disposed after all of them finished.
As the container startup takes some time, you may want to initialize the container once for all tests contained in several test classes. In this case, create a common singleton instance of your test container:
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
}
}
}
And use it in your test classes:
public class CustomerLoadTest {
@ClassRule
public static SalesTestContainer cont = SalesTestContainer.Common.INSTANCE;
...
}
- Useful container methods
-
The
TestContainer
class contains the following methods that can be used in the test code (see theCustomerLoadTest
example above):-
persistence()
– returns the reference to the Persistence interface. -
metadata()
– returns the reference to the Metadata interface. -
deleteRecord()
– this set of overloaded methods is aimed to be used in@After
methods to clean up the database after tests.
-
- Logging
-
The test container sets up logging according to the
test-logback.xml
file provided by the platform. It is contained in the root of thecuba-core-tests
artifact.If you want to configure logging levels for your tests, do the following:
-
Copy
test-logback.xml
from the platform artifact to the root of thetest
folder of your project’score
module, e.g. asmy-test-logback.xml
. -
Configure appenders and loggers in
my-test-logback.xml
. -
Add a static initializer to your test container to specify the location of your logback configuration file in the
logback.configurationFile
system property:public class MyTestContainer extends TestContainer { static { System.setProperty("logback.configurationFile", "my-test-logback.xml"); } // ... }
-
- Additional Data Stores
-
If your project uses additional data stores, you should create corresponding JDBC data sources in your test container. For example, if you have
mydb
datastore which is a PostgreSQL database, add the following method to the test container class: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); } } // ... }