Testing#
The OpenSWMM MCP Server uses pytest with
pytest-asyncio for its test suite. Tests are divided into unit tests
(which use a mock engine) and integration tests (which require the compiled
openswmm engine).
Test Layout#
tests/
__init__.py
conftest.py # Shared fixtures, mock patching, MCP client
data/ # Sample .inp files
unit/
__init__.py
conftest.py # Unit-level fixtures
data/ # Unit-test fixtures
test_session.py # SessionManager create/get/close/list
test_lifecycle.py # open_model, run_simulation, step, close
test_query.py # get_node/link/subcatch/gage_info, find_elements
test_forcing.py # set_forcing, clear_forcing, state guards
test_controls.py # control rules
test_analysis.py # statistics, mass balance, time series
test_analysis_output_reader.py # output-reader breadth (snapshot/series/attr)
test_building.py # create_model, add_node/link/subcatch, validate
test_editing.py # cascade-delete, type conversion, renames
test_nodes.py # nodes_* fine-grained accessors
test_links.py # links_* fine-grained accessors
test_subcatchments.py # subcatchments_* fine-grained accessors
test_inflows.py # external / DWF / RDII / hydrographs
test_infrastructure.py # transects / streets / inlets / LIDs
test_tables.py # time series / curves / patterns
test_hotstart.py # save/load hotstart, clone, saves_*
test_spatial_quality.py # coordinates, quality, treatment, geometry
test_geopackage_tools.py # geopackage_* tools
test_twod.py # twod_* 2D-surface tools (twod_parking_lot.inp fixture)
test_model_userflags_schema.py # model_userflag_* schema/value + model_file_path_* tools
test_forcing_climate.py # forcing_get_climate_evap_rate
test_backend_dispatch.py # openswmm vs legacy backend selection
test_phase2_wave2.py # Phase-2 wave-2 regression set
test_resources.py # All 9 swmm:// resources
test_prompts.py # All 7 prompts
test_auth.py # OAuth, JWT, MultiAuth, stdio bypass
integration/
__init__.py
conftest.py # Real engine fixtures, --run-integration gate
test_end_to_end.py # Full open -> run -> query -> close
Mock Backend Pattern#
Unit tests avoid depending on the compiled openswmm C extensions by
swapping the engine-agnostic Backend for an in-memory fake. The
shared fixtures live in tests/conftest.py and the unit-level
tests/unit/conftest.py; together they hand each test an MCP Client
wired to a server whose backend is a controllable state machine
returning deterministic data for a small synthetic network.
Tests should depend on the backend abstraction rather than on
specific engine classes (Solver, Nodes, …) — that way they
exercise the same code path for both the OpenSWMM v6 and legacy
backends. See test_backend_dispatch.py for the dual-backend
coverage pattern.
In-Memory MCP Client#
FastMCP provides a Client(transport=mcp) that connects directly to the
server object in memory, without requiring a subprocess or network socket.
This makes tests fast and deterministic.
from fastmcp import Client
@pytest.fixture
async def mcp_client(mock_engine):
from openswmm_mcp.server import mcp
async with Client(transport=mcp) as client:
yield client
Tools are invoked via:
result = await client.call_tool("lifecycle_open_model", {
"inp_path": "/tmp/test.inp",
"session_id": "test",
})
Running Unit Tests#
# Basic run
pytest tests/unit/ -v
# With coverage
pytest tests/unit/ -v --cov=openswmm_mcp --cov-report=term-missing
# Single test file
pytest tests/unit/test_lifecycle.py -v
# Single test function
pytest tests/unit/test_lifecycle.py::test_open_model_success -v
Running Integration Tests#
Integration tests require a working openswmm engine installation and
are skipped by default. The integration suite has its own
tests/integration/conftest.py that gates collection on either a
CLI flag or an environment variable — whichever is more convenient.
CLI flag:
pytest --run-integration tests/integration/ -v
Environment variable (any truthy value):
OPENSWMM_RUN_INTEGRATION=1 pytest tests/integration/ -v
Without either of these, the integration tests are collected as skipped with a clear message: “Pass –run-integration or set OPENSWMM_RUN_INTEGRATION=1 to enable.”
Integration tests use the bundled SWMM example inputs and exercise the real engine through a full open-step-query-run-close cycle.
Test Configuration#
The pyproject.toml configures pytest:
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
asyncio_mode = "auto" is set in the project’s pytest-asyncio
configuration; pytest-asyncio is a hard [dev] dependency.
Writing New Tests#
Unit tests go in
tests/unit/test_<module>.py.Use the
mcp_clientfixture for tool invocations.Use
mock_enginefor engine isolation.Assert on the tool result’s content (parsed from the MCP response).
Test both success and error paths (invalid state, missing elements, etc.).
Example:
async def test_close_model(mcp_client):
# First open a model
await mcp_client.call_tool("lifecycle_open_model", {
"inp_path": "/tmp/test.inp",
})
# Close it
result = await mcp_client.call_tool("lifecycle_close_model", {})
assert result["status"] == "closed"
Coverage Target#
The project targets 80 %+ code coverage on the openswmm_mcp package.
Coverage is tracked via pytest-cov and uploaded to Codecov in CI.