From e1c595a67fc06c634211a8cca652c40c67b3903e Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 3 Jun 2019 13:57:21 +0300 Subject: [PATCH 1/2] Polish OutputCapture and its JUnit Jupiter extension - Polish Javadoc - Improve error message in OutputCapture - Use ExtensionContext.Store in OutputCaptureExtension See gh-17049 --- .../boot/test/system/CapturedOutput.java | 2 +- .../boot/test/system/OutputCapture.java | 9 +++- .../test/system/OutputCaptureExtension.java | 48 ++++++++++++------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/CapturedOutput.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/CapturedOutput.java index 22561e71d7f..fa7e2653712 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/CapturedOutput.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/CapturedOutput.java @@ -23,7 +23,7 @@ package org.springframework.boot.test.system; * standard JUnit assertions. For example:
  * assertThat(output).contains("started"); // Checks all output
  * assertThat(output.getErr()).contains("failed"); // Only checks System.err
- * assertThat(output.getOut()).contains("ok"); // Only checks System.put
+ * assertThat(output.getOut()).contains("ok"); // Only checks System.out
  * 
* * @author Madhura Bhave 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 5fc6fd6ac4e..0261d9d3feb 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 @@ -38,6 +38,8 @@ import org.springframework.util.ClassUtils; * @author Madhura Bhave * @author Phillip Webb * @author Andy Wilkinson + * @author Sam Brannen + * @since 2.2.0 * @see OutputCaptureExtension * @see OutputCaptureRule */ @@ -125,7 +127,12 @@ 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. 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."); StringBuilder builder = new StringBuilder(); for (SystemCapture systemCapture : this.systemCaptures) { systemCapture.append(builder, filter); diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCaptureExtension.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCaptureExtension.java index e1f508e528c..235b7720def 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCaptureExtension.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/system/OutputCaptureExtension.java @@ -22,19 +22,21 @@ 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; /** - * JUnit 5 {@code @Extension} to capture {@link System#out System.out} and - * {@link System#err System.err}. Can be used on a test class via - * {@link ExtendWith @ExtendWith}. This extension provides {@link ParameterResolver - * parameter resolution} for a {@link CapturedOutput} instance which can be used to assert - * that the correct output was written. + * 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, add {@link ExtendWith @ExtendWith} and inject the {@link CapturedOutput} as an - * argument to your test class constructor or test method: + * 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)
@@ -42,7 +44,15 @@ import org.junit.jupiter.api.extension.ParameterResolver;
  *
  *     @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");
  *     }
  *
  * }
@@ -51,36 +61,35 @@ import org.junit.jupiter.api.extension.ParameterResolver;
  * @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
@@ -90,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()));
 	}
 
 }

From 41957ec2adf64824a49fc2a2f620eb89d9d00b36 Mon Sep 17 00:00:00 2001
From: Andy Wilkinson 
Date: Thu, 18 Jul 2019 17:46:28 +0100
Subject: [PATCH 2/2] 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 0261d9d3feb..d9a5b002e43 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 654a62e2cd7..736bdc53ca0 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 2eed7f73c20..462eb4f9bdf 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())); } }