From 41957ec2adf64824a49fc2a2f620eb89d9d00b36 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 18 Jul 2019 17:46:28 +0100 Subject: [PATCH] Polish "Polish OutputCapture and its JUnit Jupiter extension" See gh-17049 --- .../boot/test/system/OutputCapture.java | 7 +-- .../testsupport/system/OutputCapture.java | 3 +- .../system/OutputCaptureExtension.java | 58 +++++++++++++++---- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java index 0261d9d3fe..d9a5b002e4 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCapture.java @@ -39,7 +39,6 @@ import org.springframework.util.ClassUtils; * @author Phillip Webb * @author Andy Wilkinson * @author Sam Brannen - * @since 2.2.0 * @see OutputCaptureExtension * @see OutputCaptureRule */ @@ -128,11 +127,7 @@ class OutputCapture implements CapturedOutput { private String get(Predicate filter) { Assert.state(!this.systemCaptures.isEmpty(), - "No system captures found. When using JUnit 4, ensure that you have " - + "registered the OutputCaptureRule via @ClassRule or @Rule " - + "and that the field is public. " - + "When using JUnit Jupiter, ensure that you have registered " - + "the OutputCaptureExtension via @ExtendWith."); + "No system captures found. Please check your output capture registration."); StringBuilder builder = new StringBuilder(); for (SystemCapture systemCapture : this.systemCaptures) { systemCapture.append(builder, filter); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCapture.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCapture.java index 654a62e2cd..736bdc53ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCapture.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCapture.java @@ -111,7 +111,8 @@ class OutputCapture implements CapturedOutput { } private String get(Predicate filter) { - Assert.state(!this.systemCaptures.isEmpty(), "No system captures found. Check that you have used @ExtendWith."); + Assert.state(!this.systemCaptures.isEmpty(), + "No system captures found. Please check your output capture registration."); StringBuilder builder = new StringBuilder(); for (SystemCapture systemCapture : this.systemCaptures) { systemCapture.append(builder, filter); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCaptureExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCaptureExtension.java index 2eed7f73c2..462eb4f9bd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCaptureExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/system/OutputCaptureExtension.java @@ -20,47 +20,76 @@ import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; /** - * Internal JUnit 5 {@code @Extension} to capture {@link System#out System.out} and - * {@link System#err System.err}. + * JUnit Jupiter {@code @Extension} to capture {@link System#out System.out} and + * {@link System#err System.err}. Can be registered for an entire test class or for an + * individual test method via {@link ExtendWith @ExtendWith}. This extension provides + * {@linkplain ParameterResolver parameter resolution} for a {@link CapturedOutput} + * instance which can be used to assert that the correct output was written. + *

+ * To use with {@link ExtendWith @ExtendWith}, inject the {@link CapturedOutput} as an + * argument to your test class constructor, test method, or lifecycle methods: + * + *

+ * @ExtendWith(OutputCaptureExtension.class)
+ * class MyTest {
+ *
+ *     @Test
+ *     void test(CapturedOutput output) {
+ *         System.out.println("ok");
+ *         assertThat(output).contains("ok");
+ *         System.err.println("error");
+ *     }
+ *
+ *     @AfterEach
+ *     void after(CapturedOutput output) {
+ *         assertThat(output.getOut()).contains("ok");
+ *         assertThat(output.getErr()).contains("error");
+ *     }
+ *
+ * }
+ * 
* * @author Madhura Bhave * @author Phillip Webb * @author Andy Wilkinson + * @author Sam Brannen * @since 2.2.0 + * @see CapturedOutput */ public class OutputCaptureExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver { - private final OutputCapture outputCapture = new OutputCapture(); - OutputCaptureExtension() { // Package private to prevent users from directly creating an instance. } @Override public void beforeAll(ExtensionContext context) throws Exception { - this.outputCapture.push(); + getOutputCapture(context).push(); } @Override public void afterAll(ExtensionContext context) throws Exception { - this.outputCapture.pop(); + getOutputCapture(context).pop(); } @Override public void beforeEach(ExtensionContext context) throws Exception { - this.outputCapture.push(); + getOutputCapture(context).push(); } @Override public void afterEach(ExtensionContext context) throws Exception { - this.outputCapture.pop(); + getOutputCapture(context).pop(); } @Override @@ -70,9 +99,16 @@ public class OutputCaptureExtension } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { - return this.outputCapture; + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return getOutputCapture(extensionContext); + } + + private OutputCapture getOutputCapture(ExtensionContext context) { + return getStore(context).getOrComputeIfAbsent(OutputCapture.class); + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass())); } }