How to mock the domain center in an onion architecture in Python

Define a repository interface and an in-memory mock to test domain services without touching infrastructure.

Easy Python 3.9+ Aug 9, 2026 System design patterns 13 views 0 copies

Python code

57 lines
Python 3.9+
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, List, Optional


@dataclass
class Order:
    id: int
    customer: str
    items: List[str]
    total: float


class OrderRepository(ABC):
    @abstractmethod
    def find_by_id(self, order_id: int) -> Optional[Order]:
        pass

    @abstractmethod
    def save(self, order: Order) -> None:
        pass


class InMemoryOrderRepository(OrderRepository):
    def __init__(self) -> None:
        self._orders: Dict[int, Order] = {}

    def find_by_id(self, order_id: int) -> Optional[Order]:
        return self._orders.get(order_id)

    def save(self, order: Order) -> None:
        self._orders[order.id] = order


class OrderService:
    def __init__(self, repo: OrderRepository) -> None:
        self._repo = repo

    def get_order_total(self, order_id: int) -> float:
        order = self._repo.find_by_id(order_id)
        if order is None:
            raise ValueError(f"Order {order_id} not found")
        # Domain logic: apply 10% discount for large orders
        total = order.total
        if len(order.items) >= 3:
            total *= 0.9
        return round(total, 2)


if __name__ == "__main__":
    repo = InMemoryOrderRepository()
    repo.save(Order(id=1, customer="Alice", items=["book", "pen", "notebook"], total=50.00))
    repo.save(Order(id=2, customer="Bob", items=["laptop"], total=1200.00))

    service = OrderService(repo)
    print(f"Order 1 total after discount: ${service.get_order_total(1)}")
    print(f"Order 2 total after discount: ${service.get_order_total(2)}")

Output

stdout
Order 1 total after discount: $45.0
Order 2 total after discount: $1200.0

How it works

The OrderRepository abstract base class defines the contract for data access, letting the OrderService depend on an abstraction rather than a concrete database. InMemoryOrderRepository provides an isolated mock implementation using a plain dictionary — perfect for unit tests and local runs. The OrderService applies domain logic (10% discount for orders with 3+ items) without any knowledge of where orders are stored. Because the service only knows about the interface, swapping the mock for a real SQL or API-backed repository requires no changes to the domain core.

Common mistakes

  • Missing the `@abstractmethod` decorator on interface methods, which breaks abstraction enforcement.
  • Using a concrete repository class directly in the service constructor instead of the abstract type.
  • Forgetting to reset in-memory state between tests, causing cross-test leakage.

Variations

  1. Use `unittest.mock.Mock` in tests instead of a hand-written in-memory repository.
  2. Add a `delete` method to the repository interface for completeness.

Real-world use cases

  • Unit testing a domain service with a lightweight in-memory repository instead of a test database.
  • Prototyping an order workflow locally before wiring up a production database.
  • Simulating repository behavior in performance tests without external infrastructure.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.