pytest’s capfd fixture lets you grab anything printed to standard output or standard error during your tests, which is crucial for verifying that your code behaves as expected when it talks to the outside world.
Let’s see capfd in action. Imagine you have a simple function that prints to stdout:
# my_module.py
def greet(name):
print(f"Hello, {name}!")
def warn(message):
import sys
print(f"Warning: {message}", file=sys.stderr)
Now, how do we test this with capfd?
# test_my_module.py
import pytest
from my_module import greet, warn
def test_greet(capfd):
greet("Alice")
out, err = capfd.readouterr()
assert out == "Hello, Alice!\n"
assert err == ""
def test_warn(capfd):
warn("Something might be wrong.")
out, err = capfd.readouterr()
assert out == ""
assert err == "Warning: Something might be wrong.\n"
When you run pytest, it injects the capfd fixture into your test functions. This fixture internally hooks into Python’s sys.stdout and sys.stderr streams. When print() is called, it no longer goes to your terminal directly; instead, capfd captures it. The readouterr() method then returns a tuple containing the captured stdout and stderr as strings.
The core problem capfd solves is the difficulty of asserting on side effects that involve I/O. Without it, you’d have to redirect streams manually before running the code and then restore them, which is cumbersome and error-prone. capfd automates this redirection and provides a clean API to access the captured output.
Internally, capfd works by creating new file-like objects that replace sys.stdout and sys.stderr for the duration of the test. These new objects write to in-memory buffers. When readouterr() is called, capfd returns the contents of these buffers and then restores the original sys.stdout and sys.stderr. This ensures that your captured output is isolated to the specific test function and doesn’t interfere with other tests or pytest’s own output.
The capfd fixture is an instance of pytest.CaptureFixture. It has a few useful methods beyond readouterr():
capture_output(): This context manager allows you to capture output within a specific block of code.def test_capture_block(capfd): with capfd.capture_output() as captured: print("Inside block") assert captured.stdout == "Inside block\n"setattr(attr_name, value): You can temporarily change attributes ofsys.stdoutorsys.stderrif needed, though this is less common.
The captured strings include the trailing newline characters that print() typically adds. This is important to remember when writing your assertions. If your code prints without a newline (e.g., print("no newline", end="")), your assertion should reflect that.
One subtle behavior of capfd is how it interacts with other pytest fixtures that might also manipulate streams, such as capsys. capfd is generally preferred because it captures output from file descriptors (like sys.stdout and sys.stderr), which is more robust than capsys, which relies on stream wrappers and might miss output from certain C extensions or lower-level I/O operations. If you find capsys isn’t capturing something, try capfd.
The capfd fixture effectively gives you a direct line to your program’s console output, turning what would normally be a visual inspection into a concrete, verifiable assertion.
The next thing you’ll likely want to test is how your code handles exceptions, which often involves checking stderr for error messages.