Open spring-projects-issues opened 8 years ago
Bulk closing outdated, unresolved issues. Please, reopen if still relevant.
This is related to #13352 and #18490.
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).
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
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.
@TestFixtureComponent
public class MockEmailServer implements EmailServer {
private final Logger logger = LoggerFactory.getLogger(MockEmailServer.class);
@PostConstruct
void init() {
logger.warn("** ++ MockEmailServer In Use ++ **");
}
@Override
public boolean send(Message message) throws EmailServerUnsendableMessageException {
return true;
}
}
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.
@TestFixtureComponent
public class DatabaseManager {
private final SchemaManagerImpl schemaManager;
private final Map<String, Set<TestDataLoader>> loaders;
private final JdbcTemplate jdbcTemplate;
DatabaseManager(
SchemaManagerImpl schemaManager,
Optional<List<TestDataLoader>> optionalLoaders,
JdbcTemplate jdbcTemplate) {
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. */
public void dropSchemas() {
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.
*/
public void rebuildSchemasAndLoadTestData() {
// this.dropSchemas();
for (Schema schema : schemaManager.getSchemas()) {
schemaManager.rebuild(schema);
var loader = 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.
*/
public interface ClockProvider {
Clock clock();
}
/** Default Clock provider for the application. It returns the same clock used by Instant.now() */
@Component
class DefaultClockProvider implements ClockProvider {
@Override
public Clock clock() {
return Clock.systemUTC();
}
}
notice that I have to use @Primary
and @TestFixtureComponent
on my test fixture implementation that provides a jsr 310 extras mutable clock.
@Primary
@TestFixtureComponent
class TestClockProvider implements ClockProvider {
private final Logger logger = LoggerFactory.getLogger(TestClockProvider.class);
@PostConstruct
void init() {
logger.warn("** ++ TestClockProvider with a Mutable Clock In Use ++ **");
}
@Override
public Clock clock() {
return MutableClock.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
@Configuration
class ClockConfig {
@Bean
Clock clock(ClockProvider clockProvider) {
return clockProvider.clock();
}
}
TimeMachine class below can be autowired into test classes and used to move the clock forwards, backwards ... etc.
@TestFixtureComponent
@ConditionalOnBean(TestClockProvider.class)
public class TimeMachine {
private final MutableClock clock;
public TimeMachine(Clock clock) {
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
*/
public void advanceSeconds(long seconds) {
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
@TestFixture(exclude="com.exmaple.do.not.apply.in.this.package")
class TestClockProvider implements ClockProvider
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.
The code is in https://github.com/mdeinum/spring-framework/commit/fd552c7bbbf06d5d8ca040514f774ae386de2697.
Thanks for sharing your proof of concept, @mdeinum!
We'll take that into consideration if we decide to implement this new feature.
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 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".
Caleb Cushing opened SPR-14034 and commented
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.
Related Issues
13352 Publish TestStarted and TestCompleted events in the TestContext framework
18490 Publish TestExecutionListener events to the ApplicationContext under test
No further details from SPR-14034