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
Add NotWorking test extension (#551 / #546) #546
Add NotWorking test extension (#551 / #546) #546
Conversation
cd935b4
to
bd63717
Compare
bd63717
to
a132c5f
Compare
Could you tell me what the purpose of the extension is? What is the difference between a failing test running but not breaking and a test not running at all? |
Let's imagine there is a bug in your application:
With the proposed
|
src/test/java/org/junitpioneer/jupiter/NotWorkingExtensionTests.java
Outdated
Show resolved
Hide resolved
src/main/java/org/junitpioneer/jupiter/NotWorkingExtension.java
Outdated
Show resolved
Hide resolved
public void afterEach(ExtensionContext context) throws Exception { | ||
NotWorking annotation = getNotWorkingAnnotation(context); | ||
|
||
// null if extension should ignore test result |
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.
Don't really see the thought process behind this statement. I thought this value can never be null
because for the extension to get invoked during the afterEach
phase it must have been invoked during the beforeEach
phase.
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.
Oh, right - this is because of assumptions? In which case it makes me wonder why aren't you storing the exception object itself instead of a boolean
?
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.
Currently both handleTestExecutionException
and handleAfterEachMethodExecutionException
check the exception type and for test abort exceptions directly throw them.
If the exception object was put in the store, the abort exception check would have to be repeated in the afterEach
method. I am not sure if removing the check from handleTestExecutionException
and handleAfterEachMethodExecutionException
would be a good idea, maybe JUnit behaves differently depending on from where an exception is thrown (i.e. handleTestExecutionException
, handleAfterEachMethodExecutionException
or afterEach
).
I have added some documentation to the EXCEPTION_OCCURRED_STORE_KEY
field, does that help? Or would you still prefer to store the exception object?
public NotWorkingExtension() { | ||
} | ||
|
||
private Store getStore(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.
To be honest, I'd inline this method, but that's just my personal preference.
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 you don't mind I would like to keep the method for now to avoid having to repeat the getStore(NAMESPACE)
part, which is a bit more verbose.
We talked about this at our last team meeting and want you to invite to take part in a discussion to think about this and similar use cases in a more global view. Feel free to write down your thoughts and ideas in #550 - thank you! |
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.
Thank you for the PR.
I really like the documentation text about when to use it and especially when not. Maybe a link to assertThrows
can be added.
@Michael1993 has already stated his thoughts about the technical implementation.
One minor formal thing: Some time ago we decided that we always want to have an issue on which PRs (like this) are based on to avoid discussing non-implementation related things in the issue instread in the PR.
edit: I've created #551 for this.
Thanks a lot to all of you for being so welcoming and providing such helpful review comments (here and on my other pull requests)! I have tried to address most of the feedback, but left a comment for the refactoring of the value stored in the JUnit Store. I also don't mind if you want to keep this pull request open until the discussion in #551 has come to a conclusion. Happy Christmas Holidays! (and no hurry to respond to my comment) |
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.
Thank you @Marcono1234, good work! It's great to see PRs that cover all the bases (from code to tests to documentation, etc.). There are a bunch of comments below. I'm also not too happy with the name - we can discuss that on the issue.
src/main/java/org/junitpioneer/jupiter/NotWorkingExtension.java
Outdated
Show resolved
Hide resolved
class NotWorkingExtension implements Extension, TestExecutionExceptionHandler, LifecycleMethodExecutionExceptionHandler, | ||
BeforeEachCallback, AfterEachCallback { |
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 was surprised by how many extension points are involved here and checked the existing extension points, looking for a simpler approach. I think I found it with InvocationInterceptor
, where all you need to do is proceed with the invocation and then invert the result, i.e. fail on a successful execution and abort on failure. Here's a proof of concept:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.TestAbortedException;
import java.lang.reflect.Method;
import static org.junit.jupiter.api.Assertions.fail;
public class LabTests {
@Test
@ExtendWith(InvertResultExtension.class)
void test() {
System.out.println("Hi!");
fail();
}
private static class InvertResultExtension implements InvocationInterceptor {
@Override
public void interceptTestMethod(Invocation<Void> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
invert(invocation);
}
@Override
public <T> T interceptTestFactoryMethod(Invocation<T> invocation, ReflectiveInvocationContext<Method> invocationContext, ExtensionContext extensionContext) throws Throwable {
return invert(invocation);
}
private <T> T invert(Invocation<T> invocation) {
try {
invocation.proceed();
} catch (Throwable ex) {
throw new TestAbortedException("Test failed as expected.", ex);
}
throw new AssertionFailedError("Test unexpectedly passed.");
}
}
}
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.
That seems to work for nearly all cases except for exceptions thrown in an @AfterEach
method, which would then probably require similarly complex logic as it is currently implemented.
Should I switch to InvocationInterceptor
nonetheless?
src/test/java/org/junitpioneer/jupiter/NotWorkingExtensionTests.java
Outdated
Show resolved
Hide resolved
src/test/java/org/junitpioneer/testkit/assertion/TestCaseAssertBase.java
Show resolved
Hide resolved
Thanks for the feedback! I tried to address most of it and commented in the other cases, hopefully that is what you had in mind. |
@Marcono1234 I don't think you need to worry about What do you think? |
I was thinking of the case where the cleanup in The extension implements |
Hey @Marcono1234 Regarding this PR: we would like you to switch to |
As mentioned in my comment above However, there is a This would also include renaming the extension to |
Yes, please use
Feel free to investigate this and we will discuss it with the other maintainers when it's part of the PR.
Yes, that'd be super. Thank you! |
Because this renaming and usage of That PR is based on the commits of this one which hopefully makes reviewing the changes easier. I hope that is alright. |
Preliminary remark: I wanted to implement this either way, therefore I directly created a pull request. However, if you prefer I can also create a GitHub issue where this can be discussed first.
Adds an extension with annotation
@NotWorking
for marking test methods which are 'not working'; quoting from the documentation added by this pull request:Originally I proposed this in junit-team/junit5#2023 (comment), but I am not sure if by accident I 'hijacked' that issue, and whether I described my intentions there clear enough.
Therefore I first wanted to propose this here because I hope it has higher chances being accepted here.
I am not very familiar with JUnit extension development yet, therefore I am not sure if the way I implemented this is a clean way or if it can be improved. Any feedback is appreciated!
Proposed commit message:
PR checklist
The following checklist shall help the PR's author, the reviewers and maintainers to ensure the quality of this project.
It is based on our contributors guidelines, especially the "writing code" section.
It shall help to check for completion of the listed points.
If a point does not apply to the given PR's changes, the corresponding entry can be simply marked as done.
Documentation (general)
.adoc
file in thedocs
folder, e.g.docs/report-entries.adoc
.adoc
files)Documentation (new extension)
docs/docs-nav.yml
navigation has an entry for the new extensionpackage-info.java
contains information about the new extensionCode
Contributing
README.md
mentions the new contribution (real name optional)I hereby agree to the terms of the JUnit Pioneer Contributor License Agreement.