Writing a pytest function for checking the output on console (stdout)

Learn writing a pytest function for checking the output on console (stdout) with practical examples, diagrams, and best practices. Covers python, printing, stdout development techniques with visual...

Testing Console Output (stdout) with Pytest

Hero image for Writing a pytest function for checking the output on console (stdout)

Learn how to effectively write pytest functions to capture and assert against console output (stdout) in your Python applications, ensuring your print statements and logging work as expected.

When developing Python applications, it's common to use print() statements or logging to output information to the console (standard output, or stdout). While these are often used for debugging, sometimes the console output itself is a critical part of your application's functionality that needs to be tested. Pytest provides robust mechanisms to capture stdout, allowing you to write tests that assert the exact content or format of what your code prints.

Understanding Pytest's capsys Fixture

Pytest offers a built-in fixture called capsys (short for 'capture system output') specifically designed for capturing output to sys.stdout and sys.stderr. This fixture temporarily redirects stdout and stderr during the test execution, allowing you to inspect what your code has printed. After the test, capsys restores the original streams, ensuring that subsequent tests or the test runner itself are not affected.

sequenceDiagram
    participant TestRunner
    participant Pytest
    participant capsys
    participant YourCode

    TestRunner->>Pytest: Run test_my_function()
    Pytest->>capsys: Request 'capsys' fixture
    capsys->>Pytest: Provide 'capsys' object
    Pytest->>YourCode: Execute my_function()
    YourCode->>stdout: print("Hello")
    stdout-->>capsys: Captured output
    Pytest->>capsys: Call readouterr()
    capsys->>Pytest: Return (stdout_str, stderr_str)
    Pytest->>Pytest: Assert stdout_str == "Hello\n"
    Pytest->>capsys: Restore original stdout/stderr
    Pytest->>TestRunner: Test result

Sequence diagram illustrating how capsys captures stdout during a pytest run.

Basic Usage of capsys

To use capsys, you simply include it as an argument in your test function. The fixture provides two main methods: readouterr() and disabled(). The readouterr() method returns a named tuple (out, err) containing the captured stdout and stderr as strings. The disabled() method is a context manager that temporarily disables capturing, which can be useful if you need to print something to the actual console during a test for debugging purposes.

# my_module.py
def greet(name):
    print(f"Hello, {name}!")

def warn_user(message):
    import sys
    print(f"WARNING: {message}", file=sys.stderr)

# test_my_module.py
def test_greet_output(capsys):
    greet("World")
    captured = capsys.readouterr()
    assert captured.out == "Hello, World!\n"
    assert captured.err == ""

def test_warn_user_output(capsys):
    warn_user("Low disk space")
    captured = capsys.readouterr()
    assert captured.out == ""
    assert captured.err == "WARNING: Low disk space\n"

def test_mixed_output(capsys):
    print("This is stdout")
    import sys
    print("This is stderr", file=sys.stderr)
    captured = capsys.readouterr()
    assert captured.out == "This is stdout\n"
    assert captured.err == "This is stderr\n"

Advanced Scenarios: Disabling Capture and Partial Matches

Sometimes you might need to temporarily disable output capture within a test, or you might only want to check for a substring in the output rather than an exact match. Pytest's capsys fixture handles these scenarios gracefully. For partial matches, standard string methods like in or regular expressions can be used on the captured output string.

# test_advanced_output.py
import re

def test_disable_capture(capsys):
    print("This will be captured.")
    with capsys.disabled():
        print("This will go to the actual console.")
    print("This will be captured again.")

    captured = capsys.readouterr()
    assert "This will be captured." in captured.out
    assert "This will go to the actual console." not in captured.out

def test_partial_output_match(capsys):
    print("User 'john_doe' logged in successfully.")
    captured = capsys.readouterr()
    assert "logged in successfully" in captured.out

def test_regex_output_match(capsys):
    print("Processing file: report_2023_10_26.csv")
    captured = capsys.readouterr()
    match = re.search(r"report_\d{4}_\d{2}_\d{2}\.csv", captured.out)
    assert match is not None
    assert match.group(0) == "report_2023_10_26.csv"