Meta-Annotation Support for Testing
You can use most test-related annotations as meta-annotations to create custom composed annotations and reduce configuration duplication across a test suite.
You can use each of the following as a meta-annotation in conjunction with the TestContext framework.
- 
@BootstrapWith - 
@ContextConfiguration - 
@ContextHierarchy - 
@ContextCustomizerFactories - 
@ActiveProfiles - 
@TestPropertySource - 
@DirtiesContext - 
@WebAppConfiguration - 
@TestExecutionListeners - 
@Transactional - 
@BeforeTransaction - 
@AfterTransaction - 
@Commit - 
@Rollback - 
@Sql - 
@SqlConfig - 
@SqlMergeMode - 
@SqlGroup - 
@Repeat(only supported on JUnit 4) - 
@Timed(only supported on JUnit 4) - 
@IfProfileValue(only supported on JUnit 4) - 
@ProfileValueSourceConfiguration(only supported on JUnit 4) - 
@JUnitConfig(only supported on JUnit Jupiter) - 
@JUnitWebConfig(only supported on JUnit Jupiter) - 
@TestConstructor(only supported on JUnit Jupiter) - 
@NestedTestConfiguration(only supported on JUnit Jupiter) - 
@EnabledIf(only supported on JUnit Jupiter) - 
@DisabledIf(only supported on JUnit Jupiter) 
Consider the following example:
- 
Java
 
@RunWith(InfraRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }
@RunWith(InfraRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
If we discover that we are repeating the preceding configuration across our JUnit 4-based test suite, we can reduce the duplication by introducing a custom composed annotation that centralizes the common test configuration for Infra, as follows:
- 
Java
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Then we can use our custom @TransactionalDevTestConfig annotation to simplify the
configuration of individual JUnit 4 based test classes, as follows:
- 
Java
 
@RunWith(InfraRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(InfraRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
If we write tests that use JUnit Jupiter, we can reduce code duplication even further, since annotations in JUnit 5 can also be used as meta-annotations. Consider the following example:
- 
Java
 
@ExtendWith(InfraExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }
@ExtendWith(InfraExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
If we discover that we are repeating the preceding configuration across our JUnit Jupiter-based test suite, we can reduce the duplication by introducing a custom composed annotation that centralizes the common test configuration for Infra and JUnit Jupiter, as follows:
- 
Java
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(InfraExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
Then we can use our custom @TransactionalDevTestConfig annotation to simplify the
configuration of individual JUnit Jupiter based test classes, as follows:
- 
Java
 
@TransactionalDevTestConfig
class OrderRepositoryTests { }
@TransactionalDevTestConfig
class UserRepositoryTests { }
Since JUnit Jupiter supports the use of @Test, @RepeatedTest, ParameterizedTest,
and others as meta-annotations, you can also create custom composed annotations at the
test method level. For example, if we wish to create a composed annotation that combines
the @Test and @Tag annotations from JUnit Jupiter with the @Transactional
annotation from Infra, we could create an @TransactionalIntegrationTest annotation, as
follows:
- 
Java
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
Then we can use our custom @TransactionalIntegrationTest annotation to simplify the
configuration of individual JUnit Jupiter based test methods, as follows:
- 
Java
 
@TransactionalIntegrationTest
void saveOrder() { }
@TransactionalIntegrationTest
void deleteOrder() { }
For further details, see the Infra Annotation Programming Model wiki page.