Integration Testing
It is important to be able to perform some integration testing without requiring deployment to your application server or connecting to other enterprise infrastructure. Doing so lets you test things such as:
-
The correct wiring of your Infra IoC container contexts.
-
Data access using JDBC or an ORM tool. This can include such things as the correctness of SQL statements, Hibernate queries, JPA entity mappings, and so forth.
The TODAY Framework provides first-class support for integration testing in the
today-test
module. The name of the actual JAR file might include the release version
and might also be in the long cn.taketoday.test
form, depending on where you get
it from (see the section on Dependency Management
for an explanation). This library includes the cn.taketoday.test
package, which
contains valuable classes for integration testing with a Infra container. This testing
does not rely on an application server or other deployment environment. Such tests are
slower to run than unit tests but much faster than the equivalent Selenium tests or
remote tests that rely on deployment to an application server.
Unit and integration testing support is provided in the form of the annotation-driven Infra TestContext Framework. The TestContext framework is agnostic of the actual testing framework in use, which allows instrumentation of tests in various environments, including JUnit, TestNG, and others.
The following section provides an overview of the high-level goals of Infra integration support, and the rest of this chapter then focuses on dedicated topics:
Goals of Integration Testing
Infra integration testing support has the following primary goals:
-
To manage Infra IoC container caching between tests.
-
To provide Dependency Injection of test fixture instances.
-
To provide transaction management appropriate to integration testing.
-
To supply Infra-specific base classes that assist developers in writing integration tests.
The next few sections describe each goal and provide links to implementation and configuration details.
Context Management and Caching
The Infra TestContext Framework provides consistent loading of Infra
ApplicationContext
instances and WebApplicationContext
instances as well as caching
of those contexts. Support for the caching of loaded contexts is important, because
startup time can become an issue — not because of the overhead of Infra itself, but
because the objects instantiated by the Infra container take time to instantiate. For
example, a project with 50 to 100 Hibernate mapping files might take 10 to 20 seconds to
load the mapping files, and incurring that cost before running every test in every test
fixture leads to slower overall test runs that reduce developer productivity.
Test classes typically declare either an array of resource locations for XML or Groovy
configuration metadata — often in the classpath — or an array of component classes that
is used to configure the application. These locations or classes are the same as or
similar to those specified in web.xml
or other configuration files for production
deployments.
By default, once loaded, the configured ApplicationContext
is reused for each test.
Thus, the setup cost is incurred only once per test suite, and subsequent test execution
is much faster. In this context, the term “test suite” means all tests run in the same
JVM — for example, all tests run from an Ant, Maven, or Gradle build for a given project
or module. In the unlikely case that a test corrupts the application context and requires
reloading (for example, by modifying a bean definition or the state of an application
object) the TestContext framework can be configured to reload the configuration and
rebuild the application context before executing the next test.
See Context Management and Context Caching with the TestContext framework.
Dependency Injection of Test Fixtures
When the TestContext framework loads your application context, it can optionally
configure instances of your test classes by using Dependency Injection. This provides a
convenient mechanism for setting up test fixtures by using preconfigured beans from your
application context. A strong benefit here is that you can reuse application contexts
across various testing scenarios (for example, for configuring Infra-managed object
graphs, transactional proxies, DataSource
instances, and others), thus avoiding the
need to duplicate complex test fixture setup for individual test cases.
As an example, consider a scenario where we have a class (HibernateTitleRepository
)
that implements data access logic for a Title
domain entity. We want to write
integration tests that test the following areas:
-
The Infra configuration: Basically, is everything related to the configuration of the
HibernateTitleRepository
bean correct and present? -
The Hibernate mapping file configuration: Is everything mapped correctly and are the correct lazy-loading settings in place?
-
The logic of the
HibernateTitleRepository
: Does the configured instance of this class perform as anticipated?
See dependency injection of test fixtures with the TestContext framework.
Transaction Management
One common issue in tests that access a real database is their effect on the state of the persistence store. Even when you use a development database, changes to the state may affect future tests. Also, many operations — such as inserting or modifying persistent data — cannot be performed (or verified) outside of a transaction.
The TestContext framework addresses this issue. By default, the framework creates and
rolls back a transaction for each test. You can write code that can assume the existence
of a transaction. If you call transactionally proxied objects in your tests, they behave
correctly, according to their configured transactional semantics. In addition, if a test
method deletes the contents of selected tables while running within the transaction
managed for the test, the transaction rolls back by default, and the database returns to
its state prior to execution of the test. Transactional support is provided to a test by
using a PlatformTransactionManager
bean defined in the test’s application context.
If you want a transaction to commit (unusual, but occasionally useful when you want a
particular test to populate or modify the database), you can tell the TestContext
framework to cause the transaction to commit instead of roll back by using the
@Commit
annotation.
See transaction management with the TestContext framework.
Support Classes for Integration Testing
The Infra TestContext Framework provides several abstract
support classes that
simplify the writing of integration tests. These base test classes provide well-defined
hooks into the testing framework as well as convenient instance variables and methods,
which let you access:
-
The
ApplicationContext
, for performing explicit bean lookups or testing the state of the context as a whole. -
A
JdbcTemplate
, for executing SQL statements to query the database. You can use such queries to confirm database state both before and after execution of database-related application code, and Infra ensures that such queries run in the scope of the same transaction as the application code. When used in conjunction with an ORM tool, be sure to avoid false positives.
In addition, you may want to create your own custom, application-wide superclass with instance variables and methods specific to your project.
See support classes for the TestContext framework.