-
-
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
Introduce TestInstanceFactory extension API #1443
Conversation
@sbrannen This extension solves issues for test instances that need to be vended in a special manner; specifically this greatly simplifies testing using CDI for Weld Junit. Would love to see this or version of this implemented very soon so I thought I'd take a stab at it. Let me know your thoughts and suggested changes, if any. |
Codecov Report
@@ Coverage Diff @@
## master #1443 +/- ##
===========================================
- Coverage 92.03% 92% -0.04%
- Complexity 3416 3428 +12
===========================================
Files 316 318 +2
Lines 8173 8204 +31
Branches 696 701 +5
===========================================
+ Hits 7522 7548 +26
- Misses 485 489 +4
- Partials 166 167 +1
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, looks good so far and instantiation of nested classes is covered. Exception-handling needs some love, though.
* <p>Consult the documentation in {@link Extension} for details on | ||
* constructor requirements. | ||
* | ||
* @since 5.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5.3
:)
* @see #instantiateTestClass(Class, ExtensionContext) | ||
* @see #instantiateNestedTestClass(Class, Object, ExtensionContext) | ||
*/ | ||
@API(status = STABLE, since = "5.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's start with status = EXPERIMENTAL
and since = "5.3"
.
* @param context the current extension context; never {@code null} | ||
* @return The required test instance; never {@code null} | ||
*/ | ||
Object instantiateTestClass(Class<?> testClass, ExtensionContext context) throws Exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create an dedicated exception type that extends JUnitException
. TestInstanceCreationException
?
* @return The required test instance; never {@code null} | ||
*/ | ||
Object instantiateNestedTestClass(Class<?> testClass, Object outerInstance, ExtensionContext context) | ||
throws Exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use same dedicated exception type as above.
"Too many TestInstanceFactory extensions [%s] registered on test class: %s", factoryNames, | ||
testClass.getSimpleName()); | ||
|
||
throw new JUnitException(errorMessage); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least throw an instance of ExtensionContextException
.
if (factory == null) { | ||
return invokeTestInstanceConstructor(outerInstance, registry, extensionContext); | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else
not needed here.
return factory.instantiateTestClass(this.testClass, extensionContext); | ||
} | ||
} | ||
catch (RuntimeException exception) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both catch
are too generic. And please use BlacklistedExceptions.rethrowIfBlacklisted(cause);
/** | ||
* Integration tests that verify support for {@link TestInstanceFactory}. | ||
* | ||
* @since 5.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5.3
@sormuras Your changes have been pushed |
1baecf1
to
583377c
Compare
@@ -241,6 +241,21 @@ Examples: | |||
- `org.example.MyCondition`: deactivates the condition whose FQCN is exactly | |||
`org.example.MyCondition`. | |||
|
|||
[[extensions-test-instance-factories]] | |||
=== Test Instance Factories | |||
`{TestInstanceFactory}` defines the API for `Extensions` that wish to _provide new_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A corresponding link must also be added to /documentation/src/docs/asciidoc/link-attributes.adoc
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
[WARNING] | ||
==== | ||
Registering multiple extensions that implement `{TestInstanceFactory}` will result in an | ||
exception being thrown for all tests in the test class. It is the users responsibility to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... or test class hierarchy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
users --> user's
* @since 5.3 | ||
*/ | ||
@API(status = EXPERIMENTAL, since = "5.3") | ||
public class TestInstanceCreationException extends JUnitException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think TestInstantiationException
would be a more suitable name
* | ||
* <p>Extensions that implement {@code TestInstanceFactory} must be | ||
* registered at the class level. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class-level JavaDoc for TestInstanceFactory
should also mention that only one such extension may be registered per test class or test class hierarchy, analogous to the documentation in the User Guide.
* only attempt to create the required test instance. | ||
* | ||
* @param testClass the test class to instantiate or otherwise obtain | ||
* @param outerInstance instance of outer test class (if any) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the method is solely dedicated to instantiating a nested test class (i.e., a non-static nested class), it then does not make sense for the outerInstance
to ever be null
. Thus the documentation must state ;never {@code null}
.
* @return The required test instance; never {@code null} | ||
* @throws TestInstanceCreationException when an error occurs with the invocation of a factory | ||
*/ | ||
Object instantiateNestedTestClass(Class<?> testClass, Object outerInstance, ExtensionContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option would be to have a single such method in this new API and make the outerInstance
an Optional
.
|
||
if (parentRegistry != null) { | ||
List<TestInstanceFactory> parentFactories = parentRegistry.getExtensions(TestInstanceFactory.class); | ||
factories.removeAll(parentFactories); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are going to ignore factories registered in parent contexts -- if and only if there is a locally registered factory -- we then need to explicitly document that behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have clarified the behavior in both the javadoc and user guide.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. thanks
} | ||
catch (Throwable exception) { | ||
BlacklistedExceptions.rethrowIfBlacklisted(exception); | ||
throw exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This combination does not make sense.
What are you trying to achieve?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I misunderstood what @sormuras had suggested and exactly wash rethrowIfBlacklisted
was to be used for. I have just removed this handling entirely to let the exception propagate as normal.
|
||
try { | ||
if (outerInstance != null) { | ||
return factory.instantiateNestedTestClass(this.testClass, outerInstance, extensionContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach makes it impossible for such a constructor to accept arguments provided by a ParameterResolver
-- for example, TestInfo
, TestReporter
, mocks from Mockito, beans from Spring, etc.
We need to decide if we want this new extension API to suffer from this restriction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not believe this is a restriction that impacts the majority of use cases for factories. I believe factories are specifically for when customized construction is required by external libraries; in which case arguments from JUnit 5 sources are probably not going to be required. Also, the current extension API seems to cover these situations well and using a factory doesn't negate post-processing or any other extension.
@sbrannen I believe the changes you've requested have been pushed. Let me know if anything is still outstanding. |
@@ -65,6 +65,7 @@ endif::[] | |||
:RepetitionInfo: {javadoc-root}/org/junit/jupiter/api/RepetitionInfo.html[RepetitionInfo] | |||
:TestExecutionExceptionHandler: {javadoc-root}/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.html[TestExecutionExceptionHandler] | |||
:TestInfo: {javadoc-root}/org/junit/jupiter/api/TestInfo.html[TestInfo] | |||
:TestInstanceFactory: {javadoc-root}/org/junit/jupiter/api/extension/TestInstanceFactory.html[TestInstanceFactoryh] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove trailing "h" in link name.
TestInstanceFactoryh --> TestInstanceFactory
My comments:
Response from @kdubb:
@junit-team/junit-lambda, the team needs to reach a consensus on this before continuing with this PR. |
Thanks
I noticed an extra "h", but I think that's all for now. |
* @return the required test instance; never {@code null} | ||
* @throws TestInstantiationException when an error occurs with the invocation of a factory | ||
*/ | ||
Object instantiateTestClass(Class<?> testClass, Optional<Object> outerInstance, ExtensionContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the team already foresees that we might eventually need to add a callback into the existing parameter resolution mechanism, I think it would be better to use a parameter object, e.g. TestInstanceFactoryContext
that groups testClass
and outerInstance
. This would allow us to later add a method to the context etc. (see ParameterResolver
/ParameterContext
for an example of this approach).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@marcphilipp @sbrannen Not sure if you guys saw but I committed the changes @marcphilipp requested. Is there anything else that needs to happen? Would have love to have this in M1 if possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR is almost ready to be merged. I'm missing a test for @TestInstance(PER_CLASS)
. Could you please add that?
837d29a
to
52b168a
Compare
`TestInstanceFactory` is a new extension point that allows custom creation of test instance objects. * It must be applied at the class level * If multiple `TestInstanceFactories` are found to be registered on a test class an error is thrown * Supports @nested classes, ensuring that only one factory is registered on each level
* Declare `TestInstanceFactory` methods to throw `TestInstanceCreationException` * Mark new API as `EXPERIMENTAL` * Mark relevant files as `@since 5.3`
* Combine API methods into one; no compelling reason for separating top-level and nested factory methods. * Rename TestInstanceCreationException -> TestInstantiationException * Clarify documenation & javadoc on nested factory handling * Remove incorrect exception “filtering”
* Added `TestInstanceFactoryContext` to encapsulate the information a `TestInstanceFactory` _may_ need to instantiate a test class * Changed signature of `instantiateTestClass` to use `TestInstanceFactoryContext` instead of individual parameters
52b168a
to
21e2788
Compare
@marcphilipp Test for PER_CLASS was added and rebased onto latest master |
21e2788
to
c7241da
Compare
Thanks for adding the additional tests, @kdubb. |
This has been merged into |
Overview
TestInstanceFactory
is a new extension API that allows custom creation of test instance objects.TestInstanceFactory
extensions are found to be registered on a test class an error is thrown.@Nested
classes, ensuring that only one factory is registered on each level.Issue: #672
I hereby agree to the terms of the JUnit Contributor License Agreement.
Definition of Done
@API
annotations