Skip to content
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 withExceptions attribute to @ExpectedToFail #774

Merged
merged 25 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
28a8953
Add `onExceptions` attribute to `@ExpectedToFail`
knutwannheden Oct 7, 2023
17deba7
Address review findings
knutwannheden Oct 19, 2023
4eda00d
Update src/main/java/org/junitpioneer/jupiter/ExpectedToFail.java
knutwannheden Oct 19, 2023
a42532c
Address some more review and add documentation
knutwannheden Oct 19, 2023
7256903
Apply Spotless
knutwannheden Oct 19, 2023
2aa955f
Another round of review feedback
knutwannheden Oct 19, 2023
af5ad33
Make documentation adhere to guidelines
knutwannheden Oct 23, 2023
33a70e8
Better clarify the feature's intent in the documentation
knutwannheden Oct 24, 2023
76467b3
Update `README.md`
knutwannheden Oct 24, 2023
e721325
Update src/main/java/org/junitpioneer/jupiter/ExpectedToFail.java
knutwannheden Nov 7, 2023
7df9a9d
Merge remote-tracking branch 'upstream/main'
knutwannheden Nov 10, 2023
240d2fa
Update `README.adoc`
knutwannheden Nov 10, 2023
ff50d7e
Remove empty `README.md`
knutwannheden Nov 10, 2023
cfa7b06
Correctly order contributions
nipafx Nov 16, 2023
3506e99
Rename `onExceptions` to `withExceptions`
knutwannheden Nov 17, 2023
64c3fb7
Removed repeated warning in documentation
knutwannheden Nov 17, 2023
1f51905
Improved documentation based on review feedback
knutwannheden Nov 17, 2023
7076e18
Update docs/expected-to-fail-tests.adoc
knutwannheden Nov 29, 2023
de8e806
Added more tests and behavior for empty array
knutwannheden Nov 29, 2023
8ec98a1
Throw `ExtensionConfigurationException` on configuration error
knutwannheden Dec 1, 2023
78b9d86
Fix failing test...
knutwannheden Dec 1, 2023
1a8eb5a
Remove star import that got added by IDE
knutwannheden Dec 1, 2023
a43b3ce
Emphasize expected to fail is temporarily
beatngu13 Mar 26, 2024
f5c5e9e
Update assertion
beatngu13 Mar 26, 2024
2edbfa8
Merge remote-tracking branch 'origin/main'
beatngu13 Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The least we can do is to thank them and list some of their accomplishments here
* [Eric Everman](https://github.com/eeverman) added `@RestoreSystemProperties` and `@RestoreEnvironmentVariables` annotations to the [System Properties](https://junit-pioneer.org/docs/system-properties/) and [Environment Variables](https://junit-pioneer.org/docs/environment-variables/) extensions (#574 / #700)
* [Florian Westreicher](https://github.com/meredrica) contributed to the JSON argument source extension (#704 / #724)
* [Pёtr Andreev](https://github.com/petrandreev) added back support for NULL values to `@CartesianTestExtension` (#764 / #765)
* [Knut Wannheden](https://github.com/knutwannheden) contributed the `onExceptions` attribute of the [`@ExpectedToFail` extension](https://junit-pioneer.org/docs/expected-to-fail-tests/) (#769 / #774)
nipafx marked this conversation as resolved.
Show resolved Hide resolved

#### 2022

Expand Down
20 changes: 20 additions & 0 deletions docs/expected-to-fail-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ A custom message can be provided, explaining why the tested code is not working
include::{demo}[tag=expected_to_fail_message]
----

=== Only Abort on Expected Exceptions
beatngu13 marked this conversation as resolved.
Show resolved Hide resolved

In some cases it may be desirable to only have the test be aborted on certain expected exceptions.
In the following example a test case for `productionFeature()` has been implemented.
While the test is fully implemented, the production code has been stubbed and throws an `UnsupportedOperationException` to indicate this.

[source,java,indent=0]
----
include::{demo}[tag=expected_to_fail_onexception]
----

Once `productionFeature()` is implemented, `@ExpectedToFail` will fail the test, as no `UnsupportedOperationException` is thrown anymore.
By using `onExceptions` you can thus prevent "masking" a faulty implementation (e.g. when a value other rather than `10` is returned) with an aborted test (which would be the result when no `onExceptions` is set).

[WARNING]
====
As stated in the introduction, this feature is _not_ intended as a mechanism to assert that a test method intentionally throws an exception.
Instead, such test methods should use https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html#assertThrows(java.lang.Class,org.junit.jupiter.api.function.Executable)[JUnit's `assertThrows`] or similar.
knutwannheden marked this conversation as resolved.
Show resolved Hide resolved
knutwannheden marked this conversation as resolved.
Show resolved Hide resolved
====

== Thread-Safety

This extension is safe to use during https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution[parallel test execution].
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,17 @@ private int brokenMethod() {
return 0;
}

// tag::expected_to_fail_onexception[]
@Test
@ExpectedToFail(onExceptions = UnsupportedOperationException.class)
void testProductionFeature() {
int actual = productionFeature();
assertEquals(10, actual);
}

private int productionFeature() {
throw new UnsupportedOperationException("productionFeature() is not yet implemented");
}
// end::expected_to_fail_onexception[]

}
15 changes: 13 additions & 2 deletions src/main/java/org/junitpioneer/jupiter/ExpectedToFail.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
* of disabled tests over time.
* </p>
*
* <p>Further, the {@link #onExceptions()} attribute can be used to restrict the extension's behavior
* to specific exceptions. I.e. only if the test method end up throwing one of the specified exceptions
knutwannheden marked this conversation as resolved.
Show resolved Hide resolved
* will the test be aborted. This can, for example, be used when the production code temporarily throws
* an {@link UnsupportedOperationException} because some feature has not been implemented yet, but the
* test method is already implemented and should not fail on a failing assertion.
* </p>
*
* <p>The annotation can only be used on methods and as meta-annotation on other annotation types.
* Similar to {@code @Disabled}, it has to be used in addition to a "testable" annotation, such
* as {@link org.junit.jupiter.api.Test @Test}. Otherwise the annotation has no effect.
Expand All @@ -41,8 +48,7 @@
* <p><b>Important:</b> This annotation is <b>not</b> intended as a way to mark test methods
* which intentionally cause exceptions. Such test methods should use
* {@link org.junit.jupiter.api.Assertions#assertThrows(Class, org.junit.jupiter.api.function.Executable) assertThrows}
* or similar means to explicitly test for a specific exception class being thrown by a
* specific action.
* or similar means to explicitly test for a specific exception class being thrown by a specific action.
* </p>
*
* <p>For more details and examples, see
Expand Down Expand Up @@ -72,4 +78,9 @@
*/
String value() default "";

/**
* Specifies which exceptions are expected to be thrown and will cause the test to be aborted rather than fail.
*/
Class<? extends Throwable>[] onExceptions() default { Throwable.class };
beatngu13 marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static org.junit.jupiter.api.Assertions.fail;

import java.lang.reflect.Method;
import java.util.stream.Stream;

import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand Down Expand Up @@ -42,7 +43,12 @@ private static void invokeAndInvertResult(Invocation<Void> invocation, Extension
throw t;
}

String message = getExpectedToFailAnnotation(extensionContext).value();
ExpectedToFail expectedToFail = getExpectedToFailAnnotation(extensionContext);
if (Stream.of(expectedToFail.onExceptions()).noneMatch(clazz -> clazz.isInstance(t))) {
fail("Test marked as 'expected to fail' failed with an unexpected exception", t);
beatngu13 marked this conversation as resolved.
Show resolved Hide resolved
}

String message = expectedToFail.value();
if (message.isEmpty()) {
message = "Test marked as temporarily 'expected to fail' failed as expected";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,29 @@ void failsOnWorkingTest() {
.hasMessage("Test marked as 'expected to fail' succeeded; remove @ExpectedToFail from it");
}

@Test
void doesNotAbortOnTestThrowingExpectedException() {
ExecutionResults results = PioneerTestKit.executeTestMethod(ExpectedToFailTestCases.class, "expectedException");
assertThat(results)
.hasSingleStartedTest()
.whichAborted()
.withExceptionInstanceOf(TestAbortedException.class)
.hasMessage("Test marked as temporarily 'expected to fail' failed as expected")
.hasCauseInstanceOf(UnsupportedOperationException.class);
}

@Test
void failsOnTestThrowingUnexpectedException() {
ExecutionResults results = PioneerTestKit
.executeTestMethod(ExpectedToFailTestCases.class, "unexpectedException");
assertThat(results)
.hasSingleStartedTest()
.whichFailed()
.withExceptionInstanceOf(AssertionFailedError.class)
.hasMessage("Test marked as 'expected to fail' failed with an unexpected exception")
.hasCauseInstanceOf(AssertionFailedError.class);
}

@Test
void doesNotAbortOnBeforeEachTestFailure() {
ExecutionResults results = PioneerTestKit
Expand Down Expand Up @@ -219,6 +242,18 @@ void working() {
// Does not cause failure or error
}

@Test
@ExpectedToFail(onExceptions = { IllegalStateException.class, UnsupportedOperationException.class })
void expectedException() {
throw new UnsupportedOperationException();
}

@Test
@ExpectedToFail(onExceptions = UnsupportedOperationException.class)
void unexpectedException() {
fail();
}

}

/**
Expand Down