New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better mechanism to define singleton containers #1441
Comments
I'm willing to take a crack at implementing this feature, but before I spend the time implementing it I wanted to discuss the idea to make sure it would be acceptable if implemented. |
Hi @aguibert, In your example, how would I be able to access the container |
Hi folks, I think that is a very much needed approach. How about solving the need for accessing a container with adding the possibility to inject the I.e. having a second approach, if the access is required: @Testcontainers
public class MyTestA {
@SharedContainerConfig
private AppContainerConfig testScenario;
@Test
public void testA() {
testScenario.foo ...
}
} |
That's a nice idea, I think I like this more. So whatever the actual implementation, we would still ultimately bind the life cycle to the JVM process, correct? Now the last thing I'm wondering about, if this should be a test framework specific implementation, or if we want the API independently (probably good enough like we have it now with the |
Since the @Testcontainers
@SharedContainerConfig(AppContainerConfig.class)
public class MyTestA {
@Test
public void testA() {
int port = AppContainerConfig.app.getFirstMappedPort();
// etc...
}
} The limitation here is that pretty much everything in the shared config class ought to be static. Conceptually, statics make more sense to me when we consider the lifecycle is JVM-wide. I think the advantage to what @sdaschner suggested is that we get an instance of the object, and can access instance fields. However, I don't think instance fields should be necessary for a JVM-wide shared config?
Correct
This is really only doable with JUnit Jupiter afaik. If people are still on JUnit 4 we could or something else, they can use the current approach with the static block. |
@aguibert Oh, only now I realized that your field was actually So, I'd go for a non-static
I'm 99% sure the same is possible in JUnit 4, at least with a custom runner and reflective lookup. But yes, as a last resort, one can always fallback to the static block & abstract class. |
Doesn't this give the same result, framework agnostic? public enum MyContainers {
INSTANCE;
public KafkaContainer kafka = new KafkaContainer();
public GenericContainer whatever = ...;
public MyContainers() {
Stream.of(kafka, whatever).parallel().start();
}
}
// ...
new KafkaClient(MyContainers.INSTANCE.kafka.getBootstrapServers()); |
Having a non-static
It would be possible with a custom JUnit 4 runner, but I don't consider that an option because a test class can only define 1 custom runner at most. I don't think this mechanism is valuable enough for it to be the one customer runner that people choose. @bsideup that's an interesting idea. Can you provide a bit more context though? Do the containers in |
@aguibert according to the spec, the enum gets initialized on the first access. |
@bsideup That approach certainly works, as well as the one that is currently documented (singleton containers), however, I'd rather have another, cleaner integration into the tool. TBH, implementing singletons using enums is also not the cleanest solution :-) WDYT? Also, we might have multiple |
@aguibert I see, yes, I actually thought about defining the semantics of having a method annotated with |
Why do you think it's not clean? I think for the JVM life cycle use case, this would be elegant, since we don't need any framework integration code.
One could use package-private Enum classes, depending on the organization of the test classes. I think if we argue about something like |
I don't mind using an enum for a singleton, but a few things I don't like about any vanilla singleton solution (enums or otherwise) is:
One benefit of using an annotation is that we could bake common user options into it, such as: @SharedContainerConfig(parallelStart = true|false, // default=true
restartPolicy = NEVER|PER_CLASS|PER_TEST) // default=NEVER If we were to do this, it would indeed seem Jupiter specific. But is that really a concern? It seems that Jupiter is the future direction of automated java tests for sure. |
JUnit always start them sequentially, the parallel trick is an advantage of this approach
But if you're going to share the configuration (including the
On the other hard, I can say that this is an advantage too :) "I can access my containers from anywhere, just with Java code, without injecting anything, integrating with the test frameworks or knowing of some annotation" :)
We can't have it like this, some containers depend on another. At least until we introduce #1404
While JUnit Platform is indeed popular, there are still many users of alternative testing frameworks, including: JUnit 4, Spock, ScalaTests or whatever they call it, TestNG, etc. |
@aguibert Just to get some context (and because I'm interested in it 🙂), on what kind of extension are you working on?
|
By this approach do you mean the shared container config, or the singleton approach? Both could take advantage of parallel start. My point was that the shared container config could automatically do the parallel start, whereas with the enum/singleton approach the user needs to specify parallel.
Yes, both solutions move the container config to a separate class. What I was trying to get at here is that a class-level anno displays that information prominently to the developer.
I think allowing direct access would be the ideal advantage. Requiring access when it is not needed seems restrictive. @bsideup I do see your points though, and I think you've provides sufficient argument against this idea where I can close out this issue.
The basic idea I'm pursuing is an extension that leverages testcontainers/docker for the application under test, as opposed to just using testcontainers/docker for external resources. In the JavaEE world, I think we can use Docker+startup checks to eliminate a lot of the tedious things about starting/configuring traditional JavaEE app servers. This essentially turns things into black-box testing, but with the help of REST client proxies (among other technologies) we can make this more of a grey-box test. A few weeks back I published a blog post with my initial concept: https://openliberty.io/blog/2019/03/27/integration-testing-with-testcontainers.html |
@aguibert I hope I was not too pushy You have a valid concern that not everyone is aware of the parallel trick, thanks! Thanks for your proposal! It brought some good discussions and reminded us of a few things like #1404 :) Feel free to reopen it if you discover more downsides of the enum/shared class/static block approach! |
@bsideup not at all! I appreciate your willingness to discuss and entertain suggestions from random users like myself. #1404 looks interesting. I've added a new comment/suggestion over there. Overall, I really like field-based declaration approach of containers, but the problems is that when you want to orchestrate the containers the current field-based approach breaks down and we have to do these other solutions that seem more like "workarounds" than proper solutions -- even though I can't come up with any technical reasons why they can't be considered "proper solutions" 😉. I guess it's because the rest of the Testcontainers developer experience is really clean, but this aspect sticks out as an "ugly" spot |
So as this issue is closed, what was the conclusion? Is there any way creating a shared container beside adding a Why not simply introducing a |
Because it is not simple :) What's wrong with static + calling |
Nothing wrong, it could just be more elegant regarding the usual spring annotation-based config style. Nonetheless it works fine of course. |
Currently singleton containers are kind of hacky, requiring manual container start in a static init block of an extended class.
Singleton containers are useful, because it allows container startup cost to be amortized across multiple test classes for a faster overall runtime.
The current approach has 2 primary limitations:
Proposed solution
Add a new
SharedContainerConfiguration
marker interface, with a complementary@SharedContainerConfig(Class<? extends SharedContainerConfiguration>)
annotation which may be used on a test class. Shared container configuration would be processed inorg.testcontainers.junit.jupiter.TestcontainersExtension.beforeAll(ExtensionContext)
, and would start any containers that are not already started.Example usage:
The call flow would be as follows:
The text was updated successfully, but these errors were encountered: