TestContext Framework Support Classes
This section describes the various classes that support the Infra TestContext Framework.
Infra JUnit 4 Runner
The Infra TestContext Framework offers full integration with JUnit 4 through a custom
runner (supported on JUnit 4.12 or higher). By annotating test classes with
@RunWith(InfraJUnit4ClassRunner.class)
or the shorter @RunWith(InfraRunner.class)
variant, developers can implement standard JUnit 4-based unit and integration tests and
simultaneously reap the benefits of the TestContext framework, such as support for
loading application contexts, dependency injection of test instances, transactional test
method execution, and so on. If you want to use the Infra TestContext Framework with an
alternative runner (such as JUnit 4’s Parameterized
runner) or third-party runners
(such as the MockitoJUnitRunner
), you can, optionally, use
Infra support for JUnit rules instead.
The following code listing shows the minimal requirements for configuring a test class to
run with the custom Infra Runner
:
-
Java
@RunWith(InfraRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// test logic...
}
}
In the preceding example, @TestExecutionListeners
is configured with an empty list, to
disable the default listeners, which otherwise would require an ApplicationContext
to
be configured through @ContextConfiguration
.
Infra JUnit 4 Rules
The infra.test.context.junit4.rules
package provides the following JUnit
4 rules (supported on JUnit 4.12 or higher):
-
InfraClassRule
-
InfraMethodRule
InfraClassRule
is a JUnit TestRule
that supports class-level features of the Infra
TestContext Framework, whereas InfraMethodRule
is a JUnit MethodRule
that supports
instance-level and method-level features of the Infra TestContext Framework.
In contrast to the InfraRunner
, Infra rule-based JUnit support has the advantage of
being independent of any org.junit.runner.Runner
implementation and can, therefore, be
combined with existing alternative runners (such as JUnit 4’s Parameterized
) or
third-party runners (such as the MockitoJUnitRunner
).
To support the full functionality of the TestContext framework, you must combine a
InfraClassRule
with a InfraMethodRule
. The following example shows the proper way
to declare these rules in an integration test:
-
Java
// Optionally specify a non-Infra Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final InfraClassRule springClassRule = new InfraClassRule();
@Rule
public final InfraMethodRule springMethodRule = new InfraMethodRule();
@Test
public void testMethod() {
// test logic...
}
}
JUnit 4 Support Classes
The infra.test.context.junit4
package provides the following support
classes for JUnit 4-based test cases (supported on JUnit 4.12 or higher):
-
AbstractJUnit4InfraContextTests
-
AbstractTransactionalJUnit4InfraContextTests
AbstractJUnit4InfraContextTests
is an abstract base test class that integrates the
Infra TestContext Framework with explicit ApplicationContext
testing support in a
JUnit 4 environment. When you extend AbstractJUnit4InfraContextTests
, you can access a
protected
applicationContext
instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
AbstractTransactionalJUnit4InfraContextTests
is an abstract transactional extension of
AbstractJUnit4InfraContextTests
that adds some convenience functionality for JDBC
access. This class expects a javax.sql.DataSource
bean and a
PlatformTransactionManager
bean to be defined in the ApplicationContext
. When you
extend AbstractTransactionalJUnit4InfraContextTests
, you can access a protected
jdbcTemplate
instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running 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.
As mentioned in JDBC Testing Support,
AbstractTransactionalJUnit4InfraContextTests
also provides convenience methods that
delegate to methods in JdbcTestUtils
by using the aforementioned jdbcTemplate
.
Furthermore, AbstractTransactionalJUnit4InfraContextTests
provides an
executeSqlScript(..)
method for running SQL scripts against the configured DataSource
.
These classes are a convenience for extension. If you do not want your test classes
to be tied to a Infra-specific class hierarchy, you can configure your own custom test
classes by using @RunWith(InfraRunner.class) or Infra JUnit rules
.
|
InfraExtension for JUnit Jupiter
The Infra TestContext Framework offers full integration with the JUnit Jupiter testing
framework, introduced in JUnit 5. By annotating test classes with
@ExtendWith(InfraExtension.class)
, you can implement standard JUnit Jupiter-based unit
and integration tests and simultaneously reap the benefits of the TestContext framework,
such as support for loading application contexts, dependency injection of test instances,
transactional test method execution, and so on.
Furthermore, thanks to the rich extension API in JUnit Jupiter, Infra provides the following features above and beyond the feature set that Infra supports for JUnit 4 and TestNG:
-
Dependency injection for test constructors, test methods, and test lifecycle callback methods. See Dependency Injection with the
InfraExtension
for further details. -
Powerful support for conditional test execution based on SpEL expressions, environment variables, system properties, and so on. See the documentation for
@EnabledIf
and@DisabledIf
in Infra JUnit Jupiter Testing Annotations for further details and examples. -
Custom composed annotations that combine annotations from Infra and JUnit Jupiter. See the
@TransactionalDevTestConfig
and@TransactionalIntegrationTest
examples in Meta-Annotation Support for Testing for further details.
The following code listing shows how to configure a test class to use the
InfraExtension
in conjunction with @ContextConfiguration
:
-
Java
// Instructs JUnit Jupiter to extend the test with Infra support.
@ExtendWith(InfraExtension.class)
// Instructs Infra to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
Since you can also use annotations in JUnit 5 as meta-annotations, Infra provides the
@JUnitConfig
and @JUnitWebConfig
composed annotations to simplify the
configuration of the test ApplicationContext
and JUnit Jupiter.
The following example uses @JUnitConfig
to reduce the amount of configuration
used in the previous example:
-
Java
// Instructs Infra to register the InfraExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@JUnitConfig(TestConfig.class)
class SimpleTests {
@Test
void testMethod() {
// test logic...
}
}
Similarly, the following example uses @JUnitWebConfig
to create a
WebApplicationContext
for use with JUnit Jupiter:
-
Java
// Instructs Infra to register the InfraExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@JUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {
@Test
void testMethod() {
// test logic...
}
}
See the documentation for @JUnitConfig
and @JUnitWebConfig
in
Infra JUnit Jupiter Testing Annotations for further details.
Dependency Injection with the InfraExtension
The InfraExtension
implements the
ParameterResolver
extension API from JUnit Jupiter, which lets Infra provide dependency injection for test
constructors, test methods, and test lifecycle callback methods.
Specifically, the InfraExtension
can inject dependencies from the test’s
ApplicationContext
into test constructors and methods that are annotated with
Infra @BeforeTransaction
and @AfterTransaction
or JUnit’s @BeforeAll
,
@AfterAll
, @BeforeEach
, @AfterEach
, @Test
, @RepeatedTest
, @ParameterizedTest
,
and others.
Constructor Injection
If a specific parameter in a constructor for a JUnit Jupiter test class is of type
ApplicationContext
(or a sub-type thereof) or is annotated or meta-annotated with
@Autowired
, @Qualifier
, or @Value
, Infra injects the value for that specific
parameter with the corresponding bean or value from the test’s ApplicationContext
.
Infra can also be configured to autowire all arguments for a test class constructor if the constructor is considered to be autowirable. A constructor is considered to be autowirable if one of the following conditions is met (in order of precedence).
-
The constructor is annotated with
@Autowired
. -
@TestConstructor
is present or meta-present on the test class with theautowireMode
attribute set toALL
. -
The default test constructor autowire mode has been changed to
ALL
.
See @TestConstructor
for details on the use of
@TestConstructor
and how to change the global test constructor autowire mode.
If the constructor for a test class is considered to be autowirable, Infra
assumes the responsibility for resolving arguments for all parameters in the constructor.
Consequently, no other ParameterResolver registered with JUnit Jupiter can resolve
parameters for such a constructor.
|
Constructor injection for test classes must not be used in conjunction with JUnit
Jupiter’s The reason is that To use |
In the following example, Infra injects the OrderService
bean from the
ApplicationContext
loaded from TestConfig.class
into the
OrderServiceIntegrationTests
constructor.
-
Java
@JUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
@Autowired
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
Note that this feature lets test dependencies be final
and therefore immutable.
If the infra.test.constructor.autowire.mode
property is to all
(see
@TestConstructor
), we can omit the declaration of
@Autowired
on the constructor in the previous example, resulting in the following.
-
Java
@JUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
private final OrderService orderService;
OrderServiceIntegrationTests(OrderService orderService) {
this.orderService = orderService;
}
// tests that use the injected OrderService
}
Method Injection
If a parameter in a JUnit Jupiter test method or test lifecycle callback method is of
type ApplicationContext
(or a sub-type thereof) or is annotated or meta-annotated with
@Autowired
, @Qualifier
, or @Value
, Infra injects the value for that specific
parameter with the corresponding bean from the test’s ApplicationContext
.
In the following example, Infra injects the OrderService
from the ApplicationContext
loaded from TestConfig.class
into the deleteOrder()
test method:
-
Java
@JUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@Test
void deleteOrder(@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
}
}
Due to the robustness of the ParameterResolver
support in JUnit Jupiter, you can also
have multiple dependencies injected into a single method, not only from Infra but also
from JUnit Jupiter itself or other third-party extensions.
The following example shows how to have both Infra and JUnit Jupiter inject dependencies
into the placeOrderRepeatedly()
test method simultaneously.
-
Java
@JUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {
@RepeatedTest(10)
void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
@Autowired OrderService orderService) {
// use orderService from the test's ApplicationContext
// and repetitionInfo from JUnit Jupiter
}
}
Note that the use of @RepeatedTest
from JUnit Jupiter lets the test method gain access
to the RepetitionInfo
.
@Nested
test class configuration
The Infra TestContext Framework has supported the use of test-related annotations on
@Nested
test classes in JUnit Jupiter since TODAY Framework 5.0; however, until Infra
Framework 5.3 class-level test configuration annotations were not inherited from
enclosing classes like they are from superclasses.
TODAY Framework 5.3 introduced first-class support for inheriting test class
configuration from enclosing classes, and such configuration will be inherited by
default. To change from the default INHERIT
mode to OVERRIDE
mode, you may annotate
an individual @Nested
test class with
@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
. An explicit
@NestedTestConfiguration
declaration will apply to the annotated test class as well as
any of its subclasses and nested classes. Thus, you may annotate a top-level test class
with @NestedTestConfiguration
, and that will apply to all of its nested test classes
recursively.
In order to allow development teams to change the default to OVERRIDE
– for example,
for compatibility with TODAY Framework 5.0 through 5.2 – the default mode can be changed
globally via a JVM system property or a spring.properties
file in the root of the
classpath. See the "Changing the default enclosing configuration inheritance mode"
note for details.
Although the following "Hello World" example is very simplistic, it shows how to declare
common configuration on a top-level class that is inherited by its @Nested
test
classes. In this particular example, only the TestConfig
configuration class is
inherited. Each nested test class provides its own set of active profiles, resulting in a
distinct ApplicationContext
for each nested test class (see
Context Caching for details). Consult the list of
supported annotations to see
which annotations can be inherited in @Nested
test classes.
-
Java
@JUnitConfig(TestConfig.class)
class GreetingServiceTests {
@Nested
@ActiveProfiles("lang_en")
class EnglishGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hello World");
}
}
@Nested
@ActiveProfiles("lang_de")
class GermanGreetings {
@Test
void hello(@Autowired GreetingService service) {
assertThat(service.greetWorld()).isEqualTo("Hallo Welt");
}
}
}
TestNG Support Classes
The infra.test.context.testng
package provides the following support
classes for TestNG based test cases:
-
AbstractTestNGInfraContextTests
-
AbstractTransactionalTestNGInfraContextTests
AbstractTestNGInfraContextTests
is an abstract base test class that integrates the
Infra TestContext Framework with explicit ApplicationContext
testing support in a
TestNG environment. When you extend AbstractTestNGInfraContextTests
, you can access a
protected
applicationContext
instance variable that you can use to perform explicit
bean lookups or to test the state of the context as a whole.
AbstractTransactionalTestNGInfraContextTests
is an abstract transactional extension of
AbstractTestNGInfraContextTests
that adds some convenience functionality for JDBC
access. This class expects a javax.sql.DataSource
bean and a
PlatformTransactionManager
bean to be defined in the ApplicationContext
. When you
extend AbstractTransactionalTestNGInfraContextTests
, you can access a protected
jdbcTemplate
instance variable that you can use to run SQL statements to query the
database. You can use such queries to confirm database state both before and after
running 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.
As mentioned in JDBC Testing Support,
AbstractTransactionalTestNGInfraContextTests
also provides convenience methods that
delegate to methods in JdbcTestUtils
by using the aforementioned jdbcTemplate
.
Furthermore, AbstractTransactionalTestNGInfraContextTests
provides an
executeSqlScript(..)
method for running SQL scripts against the configured DataSource
.
These classes are a convenience for extension. If you do not want your test classes
to be tied to a Infra-specific class hierarchy, you can configure your own custom test
classes by using @ContextConfiguration , @TestExecutionListeners , and so on and by
manually instrumenting your test class with a TestContextManager . See the source code
of AbstractTestNGInfraContextTests for an example of how to instrument your test class.
|