Starter for testing Spring Boot applications with libraries including JUnit Jupiter, Hamcrest and Mockito
—
Console output testing utilities for capturing and asserting on system output during tests, providing comprehensive testing of application logging and console output.
Interface for accessing captured console output from tests.
/**
* Interface for captured output from System.out and System.err
* @since 2.2.0
*/
public interface CapturedOutput {
/**
* Get the captured output written to System.out
*/
String getOut();
/**
* Get the captured output written to System.err
*/
String getErr();
/**
* Get all captured output (both out and err combined)
*/
String getAll();
/**
* String representation of all captured output
*/
@Override
String toString();
}JUnit Jupiter extension for output capture integration.
/**
* JUnit Jupiter extension for capturing System.out and System.err output
* @since 2.2.0
*/
public class OutputCaptureExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver {
/**
* Set up output capture before each test method
*/
@Override
public void beforeEach(ExtensionContext context) throws Exception;
/**
* Clean up output capture after each test method
*/
@Override
public void afterEach(ExtensionContext context) throws Exception;
/**
* Resolve CapturedOutput parameter for test methods
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
/**
* Provide CapturedOutput instance for test methods
*/
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext);
}Usage Examples:
@ExtendWith(OutputCaptureExtension.class)
class OutputCaptureJupiterTest {
@Test
void captureOutput(CapturedOutput output) {
System.out.println("Hello World");
System.err.println("Error message");
assertThat(output.getOut()).contains("Hello World");
assertThat(output.getErr()).contains("Error message");
assertThat(output.getAll()).contains("Hello World", "Error message");
}
@Test
void captureLoggingOutput(CapturedOutput output) {
Logger logger = LoggerFactory.getLogger(OutputCaptureJupiterTest.class);
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
assertThat(output.getAll())
.contains("This is an info message")
.contains("This is a warning message")
.contains("This is an error message");
}
@Test
void captureSystemPropertyOutput(CapturedOutput output) {
System.setProperty("test.property", "test-value");
System.out.println("Property value: " + System.getProperty("test.property"));
assertThat(output.getOut()).contains("Property value: test-value");
}
}JUnit 4 rule for output capture (for legacy test support).
/**
* JUnit 4 rule for capturing System.out and System.err output
* @since 1.4.0
*/
public class OutputCaptureRule implements TestRule {
/**
* Create an OutputCaptureRule
*/
public OutputCaptureRule();
/**
* Apply the rule to the given test
*/
@Override
public Statement apply(Statement base, Description description);
/**
* Get the captured output written to System.out
*/
public String getOut();
/**
* Get the captured output written to System.err
*/
public String getErr();
/**
* Get all captured output (both out and err combined)
*/
public String getAll();
/**
* String representation of all captured output
*/
@Override
public String toString();
}Usage Examples:
public class OutputCaptureRuleTest {
@Rule
public OutputCaptureRule output = new OutputCaptureRule();
@Test
public void captureOutput() {
System.out.println("Hello from JUnit 4");
System.err.println("Error from JUnit 4");
assertThat(output.getOut()).contains("Hello from JUnit 4");
assertThat(output.getErr()).contains("Error from JUnit 4");
assertThat(output.getAll()).contains("Hello from JUnit 4", "Error from JUnit 4");
}
}Output capture integration with Spring Boot testing annotations.
Usage Examples:
@SpringBootTest
@ExtendWith(OutputCaptureExtension.class)
class SpringBootOutputCaptureTest {
@Autowired
private MyService myService;
@Test
void captureServiceOutput(CapturedOutput output) {
myService.performAction();
assertThat(output.getAll())
.contains("Service action started")
.contains("Service action completed");
}
@Test
void captureStartupOutput(CapturedOutput output) {
// Output capture will include Spring Boot startup messages
assertThat(output.getAll())
.contains("Started SpringBootOutputCaptureTest")
.contains("Spring Boot");
}
}
@WebMvcTest(UserController.class)
@ExtendWith(OutputCaptureExtension.class)
class WebMvcOutputCaptureTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void captureControllerOutput(CapturedOutput output) throws Exception {
when(userService.findByEmail("test@example.com"))
.thenReturn(new User("test@example.com", "Test User"));
mockMvc.perform(get("/users/test@example.com"))
.andExpect(status().isOk());
// Verify that controller logging is captured
assertThat(output.getAll()).contains("Processing user request");
}
}Advanced patterns for testing complex output scenarios.
Usage Examples:
@ExtendWith(OutputCaptureExtension.class)
class AdvancedOutputCaptureTest {
@Test
void captureMultiThreadedOutput(CapturedOutput output) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(3);
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
final int threadNum = i;
executor.submit(() -> {
try {
System.out.println("Thread " + threadNum + " starting");
Thread.sleep(100);
System.out.println("Thread " + threadNum + " finished");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await(5, TimeUnit.SECONDS);
executor.shutdown();
String allOutput = output.getAll();
assertThat(allOutput)
.contains("Thread 0 starting", "Thread 0 finished")
.contains("Thread 1 starting", "Thread 1 finished")
.contains("Thread 2 starting", "Thread 2 finished");
}
@Test
void captureExceptionOutput(CapturedOutput output) {
try {
throw new RuntimeException("Test exception with cause",
new IllegalArgumentException("Root cause"));
} catch (Exception e) {
e.printStackTrace();
}
String errOutput = output.getErr();
assertThat(errOutput)
.contains("RuntimeException: Test exception with cause")
.contains("Caused by: java.lang.IllegalArgumentException: Root cause")
.contains("at " + getClass().getName());
}
@Test
void captureProgressOutput(CapturedOutput output) {
System.out.print("Processing");
for (int i = 0; i < 5; i++) {
System.out.print(".");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(" Done!");
assertThat(output.getOut())
.contains("Processing.....")
.contains("Done!");
}
@Test
void captureFormattedOutput(CapturedOutput output) {
String name = "Alice";
int age = 30;
double salary = 75000.50;
System.out.printf("Employee: %s, Age: %d, Salary: $%.2f%n", name, age, salary);
System.out.println(String.format("Formatted data: [%10s] [%3d] [%10.2f]", name, age, salary));
assertThat(output.getOut())
.contains("Employee: Alice, Age: 30, Salary: $75000.50")
.contains("Formatted data: [ Alice] [ 30] [ 75000.50]");
}
@Test
void captureConditionalOutput(CapturedOutput output) {
boolean debugEnabled = true;
String operation = "data-processing";
System.out.println("Starting operation: " + operation);
if (debugEnabled) {
System.out.println("DEBUG: Operation details logged");
System.err.println("DEBUG: Memory usage checked");
}
System.out.println("Operation completed successfully");
assertThat(output.getOut())
.contains("Starting operation: data-processing")
.contains("DEBUG: Operation details logged")
.contains("Operation completed successfully");
assertThat(output.getErr())
.contains("DEBUG: Memory usage checked");
}
@Test
void captureNoOutput(CapturedOutput output) {
// Test that runs without producing any output
int result = 2 + 2;
assertThat(output.getOut()).isEmpty();
assertThat(output.getErr()).isEmpty();
assertThat(output.getAll()).isEmpty();
assertThat(result).isEqualTo(4);
}
@Test
void captureAndAnalyzeLogLevels(CapturedOutput output) {
Logger logger = LoggerFactory.getLogger("com.example.TestLogger");
logger.trace("This is a trace message");
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
String allOutput = output.getAll();
// Verify log levels are captured (depending on logging configuration)
assertThat(allOutput).contains("INFO");
assertThat(allOutput).contains("WARN");
assertThat(allOutput).contains("ERROR");
// Verify actual log messages
assertThat(allOutput)
.contains("This is an info message")
.contains("This is a warning message")
.contains("This is an error message");
}
}
// Example service that produces output for testing
@Service
static class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void performAction() {
logger.info("Service action started");
System.out.println("Performing business logic");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("Service action completed");
System.out.println("Action finished successfully");
}
}Guidelines for effective output capture testing:
contains() for partial matches, isEqualTo() for exact matchesUsage Examples:
@ExtendWith(OutputCaptureExtension.class)
class OutputCaptureBestPracticesTest {
@Test
void testSpecificBusinessOutput(CapturedOutput output) {
BusinessService service = new BusinessService();
service.processOrder("ORDER-123");
// Test specific business-relevant output
assertThat(output.getAll())
.contains("Processing order: ORDER-123")
.contains("Order processed successfully");
}
@Test
void testErrorScenarioOutput(CapturedOutput output) {
BusinessService service = new BusinessService();
assertThatThrownBy(() -> service.processOrder(null))
.isInstanceOf(IllegalArgumentException.class);
// Verify error logging
assertThat(output.getErr())
.contains("Invalid order ID: null");
}
@Test
void testOutputWithRegex(CapturedOutput output) {
BusinessService service = new BusinessService();
service.generateReport();
// Use regex for flexible matching
assertThat(output.getAll())
.containsPattern("Report generated at \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")
.containsPattern("Total records: \\d+");
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-test