-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Optionally provide test-scoped ExtensionContext
for test-specific callbacks in extensions
#3445
Comments
We agree that this is a valid use case that should be simpler to accomplish. We'd like to investigate whether it's feasible to inject the method-level |
Great to hear. If there is anything I can help with, I am happy to hear it. |
|
I just wanted to make my extension work with |
I created a PR at #4032. Also note that I added |
ExtensionContext
for test specific callbacks in ParameterResolver
and TestInstancePreConstructCallback
ExtensionContext
for test specific callbacks in extensions
@JojOatXGME Thanks for the PR! I don't think this can be changed in general since it could potentially break existing extensions (as you've pointed out in #1568 (comment)). The reason these extension method are always called with the class-scoped Instead, I think we need to provide a way for an extension to opt-in to getting the closest extension context or even failing if the test instance lifecycle is final class MyExtension implements ParameterResolver {
@Override
public Object resolveParameter(
ParameterContext parameterContext,
@TestScoped ExtensionContext extensionContext
) {
return extensionContext
.getStore(NAMESPACE)
.getOrComputeIfAbsent(ParameterWrapper.class)
.parameter;
}
// ...
} I'm open to other suggestions, though. |
ExtensionContext
for test specific callbacks in extensionsExtensionContext
for test-specific callbacks in extensions
@marcphilipp Sorry for my long message, I just had a lot to say to this topic. 😅 I could probably implement such annotation as you have suggested, but it might add noticeable complexity to the implementation. I also want to highlight that I think that the current behavior is rather problematic, and that the new behavior should feel more like the new default. Current Issues
While this is a desirable goal, I don't think that it has worked out in this case.
Regarding my points about the Alternative suggestionAs I have written above, I would like to have a solution which would feel more natural, i.e. more like the new default. Unfortunately, I don't have a very good idea to achieve that. However, I would at least propose to create the annotation on class level. This has the benefit that you wouldn't have to repeat the annotation an every affected callback you are using. @MethodContextOnInstanceCreation // Not sure about a good name
final class MyExtension implements ParameterResolver {
...
} Alternatively, we could also introduce a marker interface. final class MyExtension implements Extension2, ParameterResolver {
...
} I also want to note that
I would rather favor some method on the final class MyExtension implements Extension2, ParameterResolver {
@Override
public Object resolveParameter(
ParameterContext parameterContext,
ExtensionContext extensionContext
) {
if (extensionContext.getScope().isBelowTest()) {
throw new IllegalStateException();
}
...
}
} Assessing the RiskWhen I started to write this message, I was also not sure whether it might be worth to risk “breaking” the existing behavior. I am still not entirely sure about that, but I see that it would be quite risky. I put “breaking” in quotes because the current behavior isn't documented. Based on the documentation, my implementation actually feels like the more natural interpretation to me. Anyway, here are my considerations while I thought about it, just in case someone is interested. Click here to expand ...JUnit made a very similar change in #3905 concerning
There are some more methods which would change their behavior, but I think they are way less problematic.
|
Thanks for explaining your thought process so thoroughly! 👍 We'll discuss this in the team and will get back to you. I tend to agree that the new behavior should be the default. Rather than making it opt-in via an annotation, we could make it opt-out via a configuration parameter that would specify a comma-separated list of class name patterns (to support use case where the code of the problematic extension can't be edited, e.g. when it's in a third-party dependency). This could cause breakages when updating but there would be a switch that allows reverting to the old behavior and supports gradually updating one's extensions. |
Thanks. I also like the idea with the configuration parameter. I also just realized that we could also still make it opt-in at first, but already document that it will become the default. This way, extension vendors would have some time to adapt their extensions. Hopefully, it would encourage them to opt-in for future compatibility. However, I guess there might be many extensions where the authors are not actively monitoring the change log of JUnit, so not sure how much this would actually help. After the default is changed, the annotation (or what ever we used to opt-in) would become a no-op. |
Team decision: Make the new behavior opt-in via a new class-level annotation ( @JojOatXGME Do you have time to change your PR to add the annotation or should I take a stab at it? |
@marcphilipp I can try to implement it over the next week. |
This was made possible by lifting the `ParameterResolver` limitation for accessing the test-scoped `ExtensionContext` for test class constructors in #3445.
This was made possible by lifting the `ParameterResolver` limitation for accessing the test-scoped `ExtensionContext` for test class constructors in #3445.
I am currently writing a small extension which implements
ParameterResolver
to provide a few parameters which need to be shut down after the test. After reading the documentation, my first intuition was to write the extension like this:The problem with that solution is that the
ExtensionContext
is scoped for the whole test class when resolving arguments for the test class constructor, although the test usesTestInstance.Lifecycle.PER_METHOD
. As a consequence, my example above will use the same parameter for all tests, instead of creating a new one per test. The documentation does not describe a lot of details about the lifecycle of eachExtensionContext
, therefore I cannot declare it as a bug, but this behavior is very unexpected in my opinion. Since the callbacks are on the level of the individual tests, I expected theExtensionContext
to be scoped to the test as well. The same problem applies toTestInstancePreConstructCallback
. I haven't testedTestInstanceFactory
, but I guess it is also affected.The obvious workaround is to also add a
TestInstancePreDestroyCallback
which closes the parameter and removes it from the store. Note that the implementation of that method is not trivial because in contrast to the methods above, this callback receives a child store which is scoped to the test. Unfortunately, this means that calling remove on the given store directly instead of iterating over the parents would not work.Deliverables
ExtensionContext
forInvocationInterceptor.interceptTestClassConstructor(…)
ExtensionContext
forParameterResolver
while resolving constructor parametersExtensionContext
forTestInstancePreConstructCallback
ExtensionContext
forTestInstancePostProcessor
ExtensionContext
forTestInstanceFactory
-- or --
Related issues
ParameterResolver
of@MethodSource
factory method #3323The text was updated successfully, but these errors were encountered: