You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Introduce support for test-scoped beans in the TestContext framework [SPR-14034] #18606
I'd like to be able to register a bean as @Scope("test") (or @TestScope), so that after an integration test is executed this bean is cleaned up. I don't want to use @DirtiesContext because this is the only bean I want to throw away.
For the actual use case I've built a sort of "Login Manager" for selenium that knows which user is currently logged in and which roles have test users created for them. This can't be shared between tests because we only allow a single session for a user.
I looked to see if this exists in the reference documentation and couldn't find anything.
Please see the discussion in #24825. I hope there is room for reopening this issue, to introduce some kind of test-scoped beans (beans that should be getting rebuilt/replaced in the application context for each @Test method).
This is quite useful. I am working on an app that has multiple modules with complex integration tests, that essential to making sure the application works.
I am using Gradle Test Fixtures Plugin to allow modules to publish helper components and beans to make writing integration tests easier.
Test Fixture components typical exist in the same package as implementations of interfaces that define components so are able to call on package protected methods to rest state or verify state to ensure callers that depend on the test did the right thing. For example a package might a public interface FooService and a package protected implementation FooServiceImpl and a couple of package protected repositories say CustomerEntityRepository so when writing a integration tests I might need to have some helper test only components to load data, reset the database, mock out an interaction with a remote service ... etc.
I have a crated a custom annotation.
@Profile("test")
@Component@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestFixtureComponent {}
That is used to annotate components that are used as a test fixture. An example of this annotation in use is below.
In my integration tests of the email module I test with a real email server, or GreenMail. But when modules that pull in the email module as a dependency I want to use the MockEmailServer which lives in the src/testFixtures/java. Another example that is a helper for rebuilding the database schemas between tests or at the end of the execution of a collection of integration tests.
@TestFixtureComponentpublicclassDatabaseManager {
privatefinalSchemaManagerImplschemaManager;
privatefinalMap<String, Set<TestDataLoader>> loaders;
privatefinalJdbcTemplatejdbcTemplate;
DatabaseManager(
SchemaManagerImplschemaManager,
Optional<List<TestDataLoader>> optionalLoaders,
JdbcTemplatejdbcTemplate) {
this.schemaManager = schemaManager;
this.jdbcTemplate = jdbcTemplate;
this.loaders =
optionalLoaders.orElse(List.of()).stream()
.collect(Collectors.groupingBy(TestDataLoader::getSchema, Collectors.toSet()));
}
/** Destroy any schemas that this database manager knows about. */publicvoiddropSchemas() {
schemaManager
.getSchemas()
.forEach(
schema -> {
jdbcTemplate.execute("DROP SCHEMA IF EXISTS " + schema.getName() + " CASCADE");
});
}
/** * Rebuilds all schemas with Flyway and invokes all test test data loaders associated with a * schema. */publicvoidrebuildSchemasAndLoadTestData() {
// this.dropSchemas();for (Schemaschema : schemaManager.getSchemas()) {
schemaManager.rebuild(schema);
varloader = loaders.get(schema.getName());
if (loader != null) {
loader.forEach(TestDataLoader::loadData);
}
}
}
}
I think the pattern of defining integration test only components is very common so having a core spring test annotation is in the spirit of stereotype annotations ala@RestController, @Service, @Controller
In the current setup I have to base test class that turns on the test profile and contains helpful test only services such as a time machine class to create a mutable clock for testing date sensitive business logic.
@ActiveProfiles("test")
public abstract class SpringTest {
@Autowired protected TimeMachine timeMachine;
}
Since the app depends on classpath scanning and constructor type based auto wiring when a test fixture implementation of component is found dependency injection breaks and i have to rely on @Primary here is an example of how I do with for the TimeMachine mutable clock.
/** * A factory for obtaining an instance fo the java.util.clock that will be injected into by spring * into any components that require a clock to know the current time. */publicinterfaceClockProvider {
Clockclock();
}
/** Default Clock provider for the application. It returns the same clock used by Instant.now() */@ComponentclassDefaultClockProviderimplementsClockProvider {
@OverridepublicClockclock() {
returnClock.systemUTC();
}
}
notice that I have to use @Primary and @TestFixtureComponent on my test fixture implementation that provides a jsr 310 extras mutable clock.
@Primary@TestFixtureComponentclassTestClockProviderimplementsClockProvider {
privatefinalLoggerlogger = LoggerFactory.getLogger(TestClockProvider.class);
@PostConstructvoidinit() {
logger.warn("** ++ TestClockProvider with a Mutable Clock In Use ++ **");
}
@OverridepublicClockclock() {
returnMutableClock.of(Instant.now(), ZoneOffset.UTC);
}
}
There is also a @Configuration class to create a Clock Bean so that my code can just inject a java.util.Clock where it needs to know the current time. This class is in src/main/java
TimeMachine class below can be autowired into test classes and used to move the clock forwards, backwards ... etc.
@TestFixtureComponent@ConditionalOnBean(TestClockProvider.class)
publicclassTimeMachine {
privatefinalMutableClockclock;
publicTimeMachine(Clockclock) {
this.clock = (MutableClock) clock;
}
/** * Advanced the clock used by the application by a certain number of seconds. * * @param seconds the number of seconds to advance the clock by */publicvoidadvanceSeconds(longseconds) {
this.clock.add(Duration.ofSeconds(seconds));
}
}
So far in a green field code bases started in Jan 2020 with 1 developer me working on the code base part time I have 8 @TestCompnentFixtures and it took significant amount of effort to make component scanning just work and I am still running into edge cases where it does not.
What I wish for is an annotation from spring test that can do all the above for me automatically. so that I could write something like
and that would become the default clock provider in all modules that pull in a test-fixture jar when executing tests. I don't want to have to mark the test fixtures with @Primary or to activate the test profile.
@MockBean is not useful in my case because it forces the test writer to know that something in transitive dependency needs to be mocked. Lets say I have module C --> depends on Module B --> Depends on Module A. I am writing an integration test in module C therefore I need to put an @MockBean on component from module A in a test class in module C this makes writing tests in module c hard. What I want is to just have module C tests depend testFixtures from Module B and Module A have component scanning do the right thing when executing tests in module C.
I found a Spring boot annotation @TestComponent which i thought would do what I wanted but turned out to do something mysterious that after 4+ hours of googling, stackoverflow searches, I gave up on trying to make it work. Maybe what i want is doable with @TestComponent but I could not figure it out.
@sbrannen As mentioned yesterday on Twitter, I dove into my archives. I indeed did an attempt to implement a TestScope (for #4733). I rebased against master, and moved some code to a ContextCustomizer. It still lacks tests and documentation for the most part.
From time to time we have a need for this. Some of our beans are stateful, mostly due to caching. Having a test scope would allow us to reuse most of the application context between tests instead of throwing it all away with @DirtiesContext.
Currently I'm experimenting with the idea of having a BeanFactoryPostProcessor that changes scope of the bean definition of certain beans to "test". That would get rid of the need to redefine a bean with @Primary just to change the scope to "test".
Activity
spring-projects-issues commentedon Jan 12, 2019
sbrannen commentedon Jan 15, 2019
This is related to #13352 and #18490.
kodyrecords commentedon Apr 7, 2020
Please see the discussion in #24825. I hope there is room for reopening this issue, to introduce some kind of test-scoped beans (beans that should be getting rebuilt/replaced in the application context for each
@Testmethod).sbrannen commentedon Apr 8, 2020
Tentatively slated for 5.3.
If anyone has additional use cases that could benefit from test-scoped beans, please add comments to this issue.
Thanks
asaikali commentedon Apr 9, 2020
This is quite useful. I am working on an app that has multiple modules with complex integration tests, that essential to making sure the application works.
I am using Gradle Test Fixtures Plugin to allow modules to publish helper components and beans to make writing integration tests easier.
Test Fixture components typical exist in the same package as implementations of interfaces that define components so are able to call on package protected methods to rest state or verify state to ensure callers that depend on the test did the right thing. For example a package might a public interface
FooServiceand a package protected implementationFooServiceImpland a couple of package protected repositories sayCustomerEntityRepositoryso when writing a integration tests I might need to have some helper test only components to load data, reset the database, mock out an interaction with a remote service ... etc.I have a crated a custom annotation.
That is used to annotate components that are used as a test fixture. An example of this annotation in use is below.
In my integration tests of the email module I test with a real email server, or GreenMail. But when modules that pull in the email module as a dependency I want to use the
MockEmailServerwhich lives in the src/testFixtures/java. Another example that is a helper for rebuilding the database schemas between tests or at the end of the execution of a collection of integration tests.I think the pattern of defining integration test only components is very common so having a core spring test annotation is in the spirit of stereotype annotations ala
@RestController,@Service,@ControllerIn the current setup I have to base test class that turns on the test profile and contains helpful test only services such as a time machine class to create a mutable clock for testing date sensitive business logic.
Since the app depends on classpath scanning and constructor type based auto wiring when a test fixture implementation of component is found dependency injection breaks and i have to rely on
@Primaryhere is an example of how I do with for the TimeMachine mutable clock.notice that I have to use
@Primaryand@TestFixtureComponenton my test fixture implementation that provides a jsr 310 extras mutable clock.There is also a
@Configurationclass to create a Clock Bean so that my code can just inject a java.util.Clock where it needs to know the current time. This class is insrc/main/javaTimeMachine class below can be autowired into test classes and used to move the clock forwards, backwards ... etc.
So far in a green field code bases started in Jan 2020 with 1 developer me working on the code base part time I have 8
@TestCompnentFixturesand it took significant amount of effort to make component scanning just work and I am still running into edge cases where it does not.What I wish for is an annotation from spring test that can do all the above for me automatically. so that I could write something like
and that would become the default clock provider in all modules that pull in a test-fixture jar when executing tests. I don't want to have to mark the test fixtures with
@Primaryor to activate the test profile.@MockBeanis not useful in my case because it forces the test writer to know that something in transitive dependency needs to be mocked. Lets say I have module C --> depends on Module B --> Depends on Module A. I am writing an integration test in module C therefore I need to put an@MockBeanon component from module A in a test class in module C this makes writing tests in module c hard. What I want is to just have module C tests depend testFixtures from Module B and Module A have component scanning do the right thing when executing tests in module C.I found a Spring boot annotation
@TestComponentwhich i thought would do what I wanted but turned out to do something mysterious that after 4+ hours of googling, stackoverflow searches, I gave up on trying to make it work. Maybe what i want is doable with@TestComponentbut I could not figure it out.mdeinum commentedon Apr 10, 2020
@sbrannen As mentioned yesterday on Twitter, I dove into my archives. I indeed did an attempt to implement a
TestScope(for #4733). I rebased against master, and moved some code to aContextCustomizer. It still lacks tests and documentation for the most part.The code is in mdeinum@fd552c7.
sbrannen commentedon Apr 13, 2020
Thanks for sharing your proof of concept, @mdeinum!
We'll take that into consideration if we decide to implement this new feature.
marschall commentedon Nov 14, 2020
From time to time we have a need for this. Some of our beans are stateful, mostly due to caching. Having a test scope would allow us to reuse most of the application context between tests instead of throwing it all away with
@DirtiesContext.I have a similar implementation at:
https://github.com/marschall/spring-test-scope
Currently I'm experimenting with the idea of having a
BeanFactoryPostProcessorthat changes scope of the bean definition of certain beans to "test". That would get rid of the need to redefine a bean with@Primaryjust to change the scope to "test".