RomM Testing Approaches: Pytest Fixtures, Integration Tests, and Service Layer Validation
RomM employs a comprehensive pytest-based testing strategy organized into unit, service, handler, endpoint, and task layers, utilizing reusable fixtures in backend/tests/conftest.py and FastAPI's TestClient for full-stack integration testing.
RomM is a self-hosted ROM manager that relies on a robust Python backend to organize, scan, and serve game collections. The test suite located in backend/tests/ demonstrates production-grade testing patterns, combining isolated fixtures with end-to-end integration tests to ensure reliability across database operations, file system handling, and third-party API integrations.
Layered Test Architecture
The test suite is organized into five distinct layers that validate different aspects of the application:
- Unit tests: Validate pure functions and model methods in
backend/tests/utils/andbackend/tests/models/ - Service/Adapter tests: Exercise external API clients like IGDB and SteamGridDB in
backend/tests/adapters/services/ - Handler tests: Test business logic coordination in
backend/tests/handler/ - Endpoint tests: Full HTTP integration tests in
backend/tests/endpoints/ - Task tests: Background job validation in
backend/tests/tasks/
Pytest Fixtures and Test Infrastructure
RomM relies on centralized fixtures defined in backend/tests/conftest.py and module-specific fixtures to provide isolated, reproducible environments. Many fixtures use autouse=True when they must run for every test in a module, such as resetting caches between runs.
Application and Database Fixtures
The app fixture creates a full FastAPI instance with in-memory SQLite, while the client fixture provides a Starlette TestClient:
# backend/tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from backend.main import create_app
@pytest.fixture(scope="session")
def app():
return create_app()
@pytest.fixture
def client(app):
return TestClient(app)
Database isolation is handled via a SQLAlchemy session fixture defined in backend/tests/utils/test_context.py. The db_context fixture supplies a fresh session per test, rolling back transactions after each run to prevent state leakage between tests.
Authentication and Filesystem Fixtures
Authentication fixtures in backend/tests/handler/auth/test_oidc.py generate JWTs and OIDC tokens:
# Example from backend/tests/handler/auth/test_oidc.py
@pytest.fixture
def auth_headers(oidc_token):
return {"Authorization": f"Bearer {oidc_token}"}
Filesystem operations use temporary directories populated with mock ROM files. The tmp_library fixture in backend/tests/handler/filesystem/test_sync_handler.py mounts a clean library structure for each test and removes it afterward, ensuring file system tests do not contaminate the host environment.
Integration Testing with FastAPI and SocketIO
Integration tests launch the complete application stack to verify HTTP routes, authentication, and real-time communications.
Endpoint Testing
Located in backend/tests/endpoints/, these tests use the TestClient to exercise full request-response cycles:
# backend/tests/endpoints/test_tasks.py
def test_get_tasks(client, auth_headers):
resp = client.get("/api/v1/tasks", headers=auth_headers)
assert resp.status_code == 200
assert isinstance(resp.json(), list)
File upload scenarios are tested with binary fixtures, verifying multipart handling and background task initiation:
def test_upload_rom(client, auth_headers):
rom_file = open("tests/fixtures/sample.rom", "rb")
response = client.post(
"/api/v1/roms/upload",
files={"file": rom_file},
headers=auth_headers,
)
assert response.status_code == 201
data = response.json()
assert data["filename"] == "sample.rom"
WebSocket and SocketIO Testing
Real-time scan events are tested using Socket.IO test clients in backend/tests/endpoints/sockets/test_scan.py, emitting events such as scan_start and verifying asynchronous responses through the socket connection.
Service Adapter and Background Task Testing
Mocking External Services
Adapters for third-party APIs (IGDB, SteamGridDB) are unit-tested with mocked responses using unittest.mock or pytest-monkeypatch. The backend/tests/adapters/services/test_igdb.py file demonstrates this pattern:
def test_fetch_igdb_game(mock_igdb_client):
mock_igdb_client.return_value.get_game.return_value = {"id": 42, "name": "Test Game"}
service = IGDBService()
game = service.get_game(42)
assert game["name"] == "Test Game"
RQ Background Job Validation
Background tasks using the RQ (Redis Queue) framework are tested by spinning up in-process workers. The backend/tests/tasks/test_update_switch_titledb.py file contains the rq_worker fixture and tests that enqueue jobs and wait for completion:
def test_update_switch_titles(rq_worker, db_session):
task_id = update_switch_titles.delay()
result = task_id.get(timeout=10)
assert result["status"] == "completed"
Summary
- RomM's test suite in
backend/tests/uses pytest with a layered architecture covering unit, service, handler, endpoint, and task tests. - Reusable fixtures in
backend/tests/conftest.pyprovide FastAPI apps, database sessions, authentication tokens, and temporary filesystems. - Integration tests leverage FastAPI's TestClient and SocketIO test clients to validate complete HTTP and WebSocket workflows.
- External services are mocked in adapter tests, while background jobs are tested with live RQ workers in controlled environments.
Frequently Asked Questions
What testing framework does RomM use?
RomM uses pytest as its primary testing framework. The backend test suite is organized under backend/tests/ and leverages pytest fixtures for dependency injection, along with FastAPI's TestClient for HTTP integration testing according to the rommapp/romm source code.
How does RomM handle database isolation in tests?
Database isolation is achieved through SQLAlchemy session fixtures that create fresh transactions for each test and roll back changes afterward. The db_context fixture in backend/tests/utils/test_context.py ensures tests remain independent without requiring database resets between runs.
Where are the integration tests located in RomM?
Integration tests are located in backend/tests/endpoints/ for HTTP routes and backend/tests/endpoints/sockets/ for WebSocket/SocketIO events. These use the full FastAPI application instance with in-memory SQLite to test authentication, file uploads, and API responses end-to-end.
How does RomM test third-party API integrations?
Third-party adapters (IGDB, SteamGridDB) are tested in backend/tests/adapters/services/ using mocked HTTP responses via unittest.mock. This approach validates adapter logic without making actual network requests to external services, ensuring tests remain fast and deterministic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →