Utilities for capturing and verifying System.out and System.err output during test execution.
org.springframework.boot.test.system
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.system.CapturedOutput;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;Full Package: org.springframework.boot.test.system.OutputCaptureExtension
JUnit 5 extension for capturing console output.
/**
* JUnit 5 extension for capturing System.out and System.err
* Register with @ExtendWith(OutputCaptureExtension.class) on test classes
* @since 2.2.0
*/
public class OutputCaptureExtension implements
BeforeAllCallback,
AfterAllCallback,
BeforeEachCallback,
AfterEachCallback,
ParameterResolver {
// Package-private constructor - cannot be instantiated directly
// Must be registered via @ExtendWith annotation
// Automatically injects CapturedOutput parameter
// Captures from start of test method
// Restores original System.out/err after test
}Full Package: org.springframework.boot.test.system.CapturedOutput
public interface CapturedOutput extends CharSequence {
String getAll(); // Combined stdout + stderr
String getOut(); // stdout only
String getErr(); // stderr only
// CharSequence methods available:
// length(), charAt(), subSequence(), toString()
// Enables: assertThat(output).contains("text")
}Full Package: org.springframework.boot.test.system.OutputCaptureRule
JUnit 4 rule for capturing console output. Deprecated in favor of OutputCaptureExtension for JUnit 5.
/**
* JUnit 4 Rule for capturing System.out and System.err
* @since 2.2.0
* @deprecated since 4.0.0 for removal - use OutputCaptureExtension with JUnit 5 instead
*/
@Deprecated(since = "4.0.0", forRemoval = true)
public class OutputCaptureRule implements TestRule, CapturedOutput {
/**
* Apply the rule (from TestRule interface)
*/
public Statement apply(Statement base, Description description);
/**
* Get all captured output (implements CapturedOutput)
*/
public String getAll();
/**
* Get captured stdout only
*/
public String getOut();
/**
* Get captured stderr only
*/
public String getErr();
/**
* Get all captured output as string
*/
public String toString();
/**
* Verify output matches a Hamcrest matcher
* Verification performed after test method executes
* @param matcher the Hamcrest matcher
*/
public void expect(Matcher<? super String> matcher);
}Old (JUnit 4 with OutputCaptureRule):
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.system.OutputCaptureRule;
import static org.assertj.core.api.Assertions.assertThat;
public class MyTest {
@Rule
public OutputCaptureRule output = new OutputCaptureRule();
@Test
public void test() {
System.out.println("Hello");
assertThat(output.toString()).contains("Hello");
}
}New (JUnit 5 with OutputCaptureExtension):
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.system.CapturedOutput;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
class MyTest {
@Test
void test(CapturedOutput output) {
System.out.println("Hello");
assertThat(output).contains("Hello");
}
}import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.test.system.CapturedOutput;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(OutputCaptureExtension.class)
class OutputTest {
@Test
void testConsoleOutput(CapturedOutput output) {
System.out.println("Standard output");
System.err.println("Error output");
// Combined output
assertThat(output.getAll())
.contains("Standard output")
.contains("Error output");
// Separate streams
assertThat(output.getOut()).contains("Standard output");
assertThat(output.getErr()).contains("Error output");
// CharSequence methods
assertThat(output).contains("Standard");
assertThat(output).startsWith("Standard");
}
}import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ExtendWith(OutputCaptureExtension.class)
class LoggingTest {
private static final Logger log = LoggerFactory.getLogger(LoggingTest.class);
@Test
void testLogOutput(CapturedOutput output) {
log.info("Info message");
log.error("Error message");
assertThat(output)
.contains("Info message")
.contains("Error message");
}
}import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class SpringBootOutputTest {
@Autowired
private MyComponent component;
@Test
void testComponentOutput(CapturedOutput output) {
component.execute();
assertThat(output).contains("Execution completed");
}
}@Test
void testSilentOperation(CapturedOutput output) {
silentService.execute();
assertThat(output.getAll()).isEmpty();
}@Test
void testMultiLineOutput(CapturedOutput output) {
System.out.println("Line 1");
System.out.println("Line 2");
System.out.println("Line 3");
assertThat(output).contains("Line 1", "Line 2", "Line 3");
String[] lines = output.getAll().split("\n");
assertThat(lines).hasSize(3);
}@Test
void testExceptionOutput(CapturedOutput output) {
try {
throw new RuntimeException("Test exception");
} catch (RuntimeException e) {
e.printStackTrace();
}
assertThat(output.getErr())
.contains("RuntimeException")
.contains("Test exception")
.contains("at ");
}@Test
void testOutputPattern(CapturedOutput output) {
System.out.println("User ID: 12345");
System.out.println("Transaction ID: abc-def-ghi");
assertThat(output)
.containsPattern("User ID: \\d+")
.containsPattern("Transaction ID: [a-z-]+");
}@Test
void testOutputOrder(CapturedOutput output) {
System.out.println("Step 1: Initialize");
System.out.println("Step 2: Process");
System.out.println("Step 3: Finalize");
String allOutput = output.getAll();
assertThat(allOutput.indexOf("Step 1"))
.isLessThan(allOutput.indexOf("Step 2"));
assertThat(allOutput.indexOf("Step 2"))
.isLessThan(allOutput.indexOf("Step 3"));
}import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class BannerOutputTest {
@Test
void testSpringBootBanner(CapturedOutput output) {
assertThat(output)
.contains("Spring Boot")
.contains("Started");
}
}@Test
void testLogLevels(CapturedOutput output) {
Logger logger = LoggerFactory.getLogger("test");
logger.trace("Trace message");
logger.debug("Debug message");
logger.info("Info message");
logger.warn("Warning message");
logger.error("Error message");
// Check which levels were logged
String out = output.getAll();
assertThat(out).contains("Info message");
assertThat(out).contains("Warning message");
assertThat(out).contains("Error message");
}import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@Test
void testAsyncOutput(CapturedOutput output) throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Async output");
});
future.get(5, TimeUnit.SECONDS);
assertThat(output).contains("Async output");
}@Test
void testStreamOutput(CapturedOutput output) {
List.of("Item 1", "Item 2", "Item 3")
.forEach(System.out::println);
assertThat(output)
.contains("Item 1")
.contains("Item 2")
.contains("Item 3");
}@Test
void testFormattedOutput(CapturedOutput output) {
System.out.printf("Name: %s, Age: %d%n", "Alice", 30);
System.out.printf("Balance: $%.2f%n", 1234.567);
assertThat(output)
.contains("Name: Alice, Age: 30")
.contains("Balance: $1234.57");
}@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class ComponentOutputTest {
@Autowired
private ReportGenerator reportGenerator;
@Test
void testReportGeneration(CapturedOutput output) {
reportGenerator.generateReport();
assertThat(output)
.contains("Report started")
.contains("Processing records")
.contains("Report completed");
}
}@Test
void testProgressIndicator(CapturedOutput output) {
for (int i = 0; i <= 100; i += 20) {
System.out.print("\rProgress: " + i + "%");
}
System.out.println();
assertThat(output).contains("Progress: 100%");
}@Test
void testColoredOutput(CapturedOutput output) {
String red = "\u001B[31m";
String reset = "\u001B[0m";
System.out.println(red + "Error message" + reset);
assertThat(output).contains("Error message");
assertThat(output.getAll()).contains("\u001B[31m");
}@Test
void testNoStderr(CapturedOutput output) {
System.out.println("Only stdout");
assertThat(output.getOut()).isNotEmpty();
assertThat(output.getErr()).isEmpty();
}
@Test
void testNoStdout(CapturedOutput output) {
System.err.println("Only stderr");
assertThat(output.getOut()).isEmpty();
assertThat(output.getErr()).isNotEmpty();
}@Test
void testUnicodeOutput(CapturedOutput output) {
System.out.println("Unicode: \u2713 \u2717 \u263A");
System.out.println("Emoji: \uD83D\uDE80 \uD83C\uDF89");
assertThat(output)
.contains("\u2713")
.contains("\u263A");
}@Test
void testLargeOutput(CapturedOutput output) {
for (int i = 0; i < 1000; i++) {
System.out.println("Line " + i);
}
assertThat(output).contains("Line 0");
assertThat(output).contains("Line 999");
assertThat(output.getAll().split("\n")).hasSize(1000);
}@Test
void testConditionalLogging(CapturedOutput output) {
Logger logger = LoggerFactory.getLogger("test");
if (logger.isDebugEnabled()) {
logger.debug("Debug message");
}
logger.info("Info message");
assertThat(output).contains("Info message");
}@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class StartupLoggingTest {
@Test
void testApplicationStartup(CapturedOutput output) {
assertThat(output)
.contains("Starting")
.contains("Started")
.contains("Tomcat started on port");
}
}@ExtendWith(OutputCaptureExtension.class)
class MultipleTestsOutputTest {
@Test
void firstTest(CapturedOutput output) {
System.out.println("First test output");
assertThat(output).contains("First test");
}
@Test
void secondTest(CapturedOutput output) {
System.out.println("Second test output");
// Each test gets fresh CapturedOutput
assertThat(output).contains("Second test");
assertThat(output).doesNotContain("First test");
}
}@Test
void testFilteredOutput(CapturedOutput output) {
System.out.println("Important message");
System.out.println("DEBUG: internal detail");
System.out.println("Another important message");
// Verify important messages present
assertThat(output)
.contains("Important message")
.contains("Another important message");
// Optionally verify debug present (or filter it out in production)
assertThat(output).contains("DEBUG: internal detail");
}@Test
void testPrintMethods(CapturedOutput output) {
System.out.print("No newline");
System.out.print(" - ");
System.out.println("With newline");
assertThat(output).contains("No newline - With newline");
}@Test
void testWithExtractors(CapturedOutput output) {
System.out.println("Status: SUCCESS");
System.out.println("Duration: 1234ms");
assertThat(output.getAll())
.asString()
.contains("SUCCESS")
.containsPattern("Duration: \\d+ms");
}Issue: Output not captured
Issue: Output from before test captured
Issue: NullPointerException on output parameter