Testing Log Output#

FastCS uses loguru for logging. The test suite provides a loguru_caplog fixture that bridges loguru into pytest’s standard caplog mechanism, making it straightforward to assert on log messages in tests.

The loguru_caplog Fixture#

The fixture is defined in tests/conftest.py and registers a loguru sink that forwards all messages (down to TRACE level) into pytest’s caplog:

@pytest.fixture
def loguru_caplog(caplog):
    handler_id = logger.add(caplog.handler, format="{message}", level="TRACE")
    yield caplog
    logger.remove(handler_id)

Use it by adding loguru_caplog as a parameter to your test function. The yielded value is pytest’s standard caplog object, so the same assertions work:

  • loguru_caplog.text — all captured log output as a single string

  • loguru_caplog.records — list of logging.LogRecord objects, each with a .message attribute

Asserting on ERROR-level Messages#

Pass loguru_caplog to any test that exercises code that calls logger.error(...) or similar. After the code runs, assert against loguru_caplog.text:

@pytest.mark.asyncio
async def test_attr_r_update_logs_validation_error(loguru_caplog):
    attr = AttrR(Int())

    with pytest.raises(ValueError):
        await attr.update("not_an_int")

    assert "Failed to validate value" in loguru_caplog.text

The same pattern applies when a callback raises:

@pytest.mark.asyncio
async def test_attr_r_update_logs_callback_failure(loguru_caplog):
    attr = AttrR(Int())

    async def failing_callback(_value: int):
        raise RuntimeError("callback failed")

    attr.add_on_update_callback(failing_callback)

    with pytest.raises(RuntimeError):
        await attr.update(42)

    assert "On update callbacks failed" in loguru_caplog.text

Asserting on TRACE-level Messages#

log_event calls (from the Tracer mixin) emit at TRACE level and are only active when tracing is enabled on that object. The loguru_caplog fixture captures at TRACE level, so no extra setup is needed beyond enabling tracing on the object under test.

Use loguru_caplog.records and check .message on each record for precise matching:

@pytest.mark.asyncio
async def test_attr_r_update_trace_logs_when_tracing_enabled(loguru_caplog):
    """log_event emits 'Attribute set' and 'Value validated' when tracing is on."""
    attr = AttrR(Int())
    attr.enable_tracing()

    await attr.update(42)

    messages = [r.message for r in loguru_caplog.records]
    assert any("Attribute set" in m for m in messages)
    assert any("Value validated" in m for m in messages)

You can also verify that messages are absent when tracing is not enabled:

@pytest.mark.asyncio
async def test_attr_r_update_no_trace_logs_when_tracing_disabled(loguru_caplog):
    attr = AttrR(Int())

    await attr.update(42)

    messages = [r.message for r in loguru_caplog.records]
    assert not any("Attribute set" in m for m in messages)
    assert not any("Value validated" in m for m in messages)