Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add @ReportEntry for declarative TestReporter entries
This commit adds the basic features of a @ReportEntry extension. It allows annotating a test case with one or multiple key/value pairs, which are then published via `TestReporter`. PR: #183 Closes: #134 [ci skip-release]
- Loading branch information
1 parent
595298c
commit 46fe56e
Showing
8 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
:page-title: Report entries | ||
:page-description: JUnit Jupiter extension to report with annotations. | ||
|
||
You can use `@ReportEntry` as a simple way to declaratively add metadata to test methods. | ||
|
||
From the https://https://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection[JUnit 5 documentation]: | ||
|
||
> In JUnit Jupiter you should use `TestReporter` where you used to print information to `stdout` or `stderr` in JUnit 4. | ||
> Using `@RunWith(JUnitPlatform.class)` will output all reported entries to `stdout`. | ||
> In addition, some IDEs print report entries to `stdout` or display them in the user interface for test results. | ||
|
||
To see how `@ReportEntry` helps, let's first take a look at the conventional approach and then at this extension. | ||
|
||
== Standard Use of `TestReporter` | ||
|
||
From the https://https://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection[JUnit 5 documentation]: | ||
|
||
> If a constructor or method parameter is of type `TestReporter`, the `TestReporterParameterResolver` will supply an instance of `TestReporter`. | ||
> The `TestReporter` can be used to publish additional data about the current test run. | ||
> The data can be consumed via the `reportingEntryPublished()` method in a `TestExecutionListener`, allowing it to be viewed in IDEs or included in reports. | ||
|
||
So, you would use it like this: | ||
|
||
[source,java] | ||
---- | ||
@Test | ||
void reportingTest(TestReporter reporter) { | ||
reporter.publishEntry("Hello World!"); | ||
// YOUR TEST CODE HERE | ||
} | ||
---- | ||
|
||
You can have a look at https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/TestReporter.html[the official documentation for more details]. | ||
|
||
== With the extension | ||
|
||
It can be argued that publishing data about your tests should not be in your test code because it is simply not part of your test. | ||
It is meta-data. | ||
|
||
So it makes sense that you should be able to add that meta-data to your test declaratively with annotations and have JUnit take care of the publishing. | ||
This is what this extension is for! | ||
|
||
You can write... | ||
|
||
[source,java] | ||
---- | ||
@Test | ||
@ReportEntry("Hello World!") | ||
void reportingTest() { | ||
// YOUR TEST CODE HERE | ||
} | ||
---- | ||
|
||
...and the extension will publish your report entry for you! | ||
|
||
You can declare multiple report entries on the same test (the annotation is repeatable). | ||
|
||
[source,java] | ||
---- | ||
@Test | ||
@ReportEntry("foo") | ||
@ReportEntry("bar") | ||
void reportingTest() { | ||
// YOUR TEST CODE HERE | ||
} | ||
---- | ||
|
||
Just like `TestReporter::publishEntry` accepts a single string as value or a key/value pair, `@ReportEntry` accepts either a single string as value or a key/value pair: | ||
|
||
[source,java] | ||
---- | ||
@Test | ||
@ReportEntry(key = "line1", value = "Once upon a midnight dreary") | ||
@ReportEntry(key = "line2", value = "While I pondered weak and weary") | ||
void edgarAllanPoe() { | ||
// YOUR TEST CODE HERE | ||
} | ||
---- | ||
|
||
Again, just like `TestReporter::publishEntry`, if no key is given it defaults to `"value"` (yes, that's not a mixup). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2015-2020 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter; | ||
|
||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
|
||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@ExtendWith(ReportEntryExtension.class) | ||
public @interface ReportEntries { | ||
|
||
ReportEntry[] value(); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright 2015-2020 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter; | ||
|
||
import java.lang.annotation.Repeatable; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
|
||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
/** | ||
* Publish the specified key-value pair to be consumed by an | ||
* {@code org.junit.platform.engine.EngineExecutionListener} | ||
* in order to supply additional information to the reporting | ||
* infrastructure. This is funtionally identical to calling | ||
* {@link org.junit.jupiter.api.extension.ExtensionContext#publishReportEntry(String, String) ExtensionContext::publishReportEntry} | ||
* from within the test method. | ||
*/ | ||
@Repeatable(ReportEntries.class) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
@ExtendWith(ReportEntryExtension.class) | ||
public @interface ReportEntry { | ||
|
||
/** | ||
* Specifies the key of the pair that's to be published as a report entry. | ||
* Defaults to {@code "value"} and can't be blank. | ||
* | ||
* @see org.junit.jupiter.api.extension.ExtensionContext#publishReportEntry(String, String) ExtensionContext::publishReportEntry | ||
*/ | ||
String key() default "value"; | ||
|
||
/** | ||
* Specifies the value of the pair that's to be published as a report entry. | ||
* Can't be blank. | ||
* | ||
* @see org.junit.jupiter.api.extension.ExtensionContext#publishReportEntry(String, String) ExtensionContext::publishReportEntry | ||
*/ | ||
String value(); | ||
|
||
} |
36 changes: 36 additions & 0 deletions
36
src/main/java/org/junitpioneer/jupiter/ReportEntryExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* Copyright 2015-2020 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter; | ||
|
||
import static java.lang.String.format; | ||
|
||
import org.junit.jupiter.api.extension.BeforeEachCallback; | ||
import org.junit.jupiter.api.extension.ExtensionConfigurationException; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
|
||
class ReportEntryExtension implements BeforeEachCallback { | ||
|
||
@Override | ||
public void beforeEach(ExtensionContext context) throws Exception { | ||
Utils | ||
.findRepeatableAnnotation(context, ReportEntry.class) | ||
.peek(ReportEntryExtension::verifyKeyValueAreNotBlank) | ||
.forEach(entry -> context.publishReportEntry(entry.key(), entry.value())); | ||
} | ||
|
||
private static void verifyKeyValueAreNotBlank(ReportEntry entry) { | ||
if (entry.key().isEmpty() || entry.value().isEmpty()) { | ||
String message = "Report entries can't have blank key or value: { key=\"%s\", value=\"%s\" }"; | ||
throw new ExtensionConfigurationException(format(message, entry.key(), entry.value())); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
src/test/java/org/junitpioneer/jupiter/ReportEntryExtensionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
* Copyright 2015-2020 the original author or authors. | ||
* | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the Eclipse Public License v2.0 which | ||
* accompanies this distribution and is available at | ||
* | ||
* http://www.eclipse.org/legal/epl-v20.html | ||
*/ | ||
|
||
package org.junitpioneer.jupiter; | ||
|
||
import static java.util.stream.Collectors.toList; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.junit.jupiter.api.Assertions.assertAll; | ||
|
||
import java.util.AbstractMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; | ||
import org.junit.platform.engine.test.event.ExecutionEvent; | ||
import org.junit.platform.engine.test.event.ExecutionEventRecorder; | ||
|
||
public class ReportEntryExtensionTest extends AbstractJupiterTestEngineTests { | ||
|
||
@Test | ||
void explicitKey_keyAndValueAreReported() { | ||
ExecutionEventRecorder recorder = executeTestsForMethod(ReportEntriesTest.class, "explicitKey"); | ||
|
||
List<Map<String, String>> reportEntries = reportEntries(recorder); | ||
assertThat(reportEntries).hasSize(1); | ||
Map<String, String> reportEntry = reportEntries.get(0); | ||
assertThat(reportEntry).hasSize(1); | ||
assertThat(reportEntry).containsExactly(entryOf("Crow2", "While I pondered weak and weary")); | ||
} | ||
|
||
@Test | ||
void implicitKey_keyIsNamedValue() { | ||
ExecutionEventRecorder recorder = executeTestsForMethod(ReportEntriesTest.class, "implicitKey"); | ||
|
||
List<Map<String, String>> reportEntries = reportEntries(recorder); | ||
assertThat(reportEntries).hasSize(1); | ||
assertThat(reportEntries.get(0)).satisfies(reportEntry -> { | ||
assertThat(reportEntry).hasSize(1); | ||
assertThat(reportEntry).containsExactly(entryOf("value", "Once upon a midnight dreary")); | ||
}); | ||
} | ||
|
||
@Test | ||
void emptyKey_fails() { | ||
ExecutionEventRecorder recorder = executeTestsForMethod(ReportEntriesTest.class, "emptyKey"); | ||
|
||
assertThat(recorder.getFailedTestFinishedEvents()).hasSize(1); | ||
assertThat(getFirstFailuresThrowable(recorder).getMessage()) | ||
.contains("Report entries can't have blank key or value", | ||
"Over many a quaint and curious volume of forgotten lore"); | ||
} | ||
|
||
@Test | ||
void emptyValue_fails() { | ||
ExecutionEventRecorder recorder = executeTestsForMethod(ReportEntriesTest.class, "emptyValue"); | ||
|
||
assertThat(recorder.getFailedTestFinishedEvents()).hasSize(1); | ||
assertThat(getFirstFailuresThrowable(recorder).getMessage()) | ||
.contains("Report entries can't have blank key or value", "While I nodded, nearly napping"); | ||
} | ||
|
||
@Test | ||
void repeatedAnnotation_logEachKeyValuePairAsIndividualEntry() { | ||
ExecutionEventRecorder recorder = executeTestsForMethod(ReportEntriesTest.class, "repeatedAnnotation"); | ||
|
||
List<Map<String, String>> reportEntries = reportEntries(recorder); | ||
|
||
assertAll("Verifying report entries " + reportEntries, // | ||
() -> assertThat(reportEntries).hasSize(3), | ||
() -> assertThat(reportEntries).extracting(entry -> entry.size()).containsExactlyInAnyOrder(1, 1, 1), | ||
() -> assertThat(reportEntries) | ||
.extracting(entry -> entry.get("value")) | ||
.containsExactlyInAnyOrder("suddenly there came a tapping", "As if some one gently rapping", | ||
"rapping at my chamber door")); | ||
} | ||
|
||
private static List<Map<String, String>> reportEntries(ExecutionEventRecorder recorder) { | ||
return recorder | ||
.eventStream() | ||
.filter(event -> event.getType().equals(ExecutionEvent.Type.REPORTING_ENTRY_PUBLISHED)) | ||
.map(executionEvent -> executionEvent.getPayload(org.junit.platform.engine.reporting.ReportEntry.class)) | ||
.filter(Optional::isPresent) | ||
.map(Optional::get) | ||
.map(org.junit.platform.engine.reporting.ReportEntry::getKeyValuePairs) | ||
.collect(toList()); | ||
} | ||
|
||
private static Map.Entry<String, String> entryOf(String key, String value) { | ||
return new AbstractMap.SimpleEntry<>(key, value); | ||
} | ||
|
||
static class ReportEntriesTest { | ||
|
||
@Test | ||
@ReportEntry(key = "Crow2", value = "While I pondered weak and weary") | ||
void explicitKey() { | ||
} | ||
|
||
@Test | ||
@ReportEntry("Once upon a midnight dreary") | ||
void implicitKey() { | ||
} | ||
|
||
@Test | ||
@ReportEntry(key = "", value = "Over many a quaint and curious volume of forgotten lore") | ||
void emptyKey() { | ||
} | ||
|
||
@Test | ||
@ReportEntry(key = "While I nodded, nearly napping", value = "") | ||
void emptyValue() { | ||
} | ||
|
||
@Test | ||
@ReportEntry("suddenly there came a tapping") | ||
@ReportEntry("As if some one gently rapping") | ||
@ReportEntry("rapping at my chamber door") | ||
void repeatedAnnotation() { | ||
} | ||
|
||
} | ||
|
||
} |