# RomM Testing Approaches: Pytest Fixtures, Integration Tests, and Service Layer Validation

> Discover RomM's robust testing strategy using pytest fixtures and integration tests. Learn how RomM ensures quality across its full stack from unit to endpoint layers.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: testing
- Published: 2026-07-06

---

**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`](https://github.com/rommapp/romm/blob/main/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/` and `backend/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`](https://github.com/rommapp/romm/blob/main/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:

```python

# 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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/backend/tests/handler/auth/test_oidc.py) generate JWTs and OIDC tokens:

```python

# 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`](https://github.com/rommapp/romm/blob/main/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:

```python

# 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:

```python
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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/backend/tests/adapters/services/test_igdb.py) file demonstrates this pattern:

```python
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`](https://github.com/rommapp/romm/blob/main/backend/tests/tasks/test_update_switch_titledb.py) file contains the `rq_worker` fixture and tests that enqueue jobs and wait for completion:

```python
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.py`](https://github.com/rommapp/romm/blob/main/backend/tests/conftest.py) provide 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`](https://github.com/rommapp/romm/blob/main/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.