CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-test

Starter for testing Spring Boot applications with libraries including JUnit Jupiter, Hamcrest and Mockito

Pending
Overview
Eval results
Files

output-capture.mddocs/

Output Capture

Console output testing utilities for capturing and asserting on system output during tests, providing comprehensive testing of application logging and console output.

Capabilities

Captured Output Interface

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

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

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");
    }
}

Integration with Spring Boot Tests

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 Output Capture Patterns

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");
    }
}

Best Practices for Output Capture

Guidelines for effective output capture testing:

  1. Test Specific Output: Focus on testing specific, meaningful output rather than implementation details
  2. Use Appropriate Assertions: Use contains() for partial matches, isEqualTo() for exact matches
  3. Handle Timing: Be aware of timing issues in multi-threaded scenarios
  4. Separate Concerns: Test output separately from business logic when possible
  5. Consider Log Levels: Remember that captured output depends on logging configuration

Usage 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

docs

context-testing.md

core-testing.md

index.md

json-testing.md

mock-integration.md

output-capture.md

test-slices.md

web-testing.md

tile.json