Mock Google Pub/Sub publish and pull in Python

A lightweight in-memory mock of Google Pub/Sub with publisher/subscriber classes to test topic-based fan-out and message pulling without real infrastructure.

Medium Python 3.10+ Aug 9, 2026 Cloud + Python 15 views 0 copies

Python code

69 lines
Python 3.10+
import json
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Any, Callable


@dataclass
class Message:
    data: str
    attributes: dict[str, str] = field(default_factory=dict)
    message_id: str | None = None
    ack_id: str | None = None


class MockPublisher:
    def __init__(self) -> None:
        self._subscriptions: dict[str, deque[Message]] = {}

    def subscribe(self, topic: str, subscription: str) -> None:
        key = f"{topic}/{subscription}"
        self._subscriptions.setdefault(key, deque())

    def publish(self, topic: str, data: str, **attributes: Any) -> Message:
        msg = Message(data=data, attributes=attributes)
        for key in self._subscriptions:
            if key.startswith(f"{topic}/"):
                self._subscriptions[key].append(msg)
        return msg


class MockSubscriber:
    def __init__(self, publisher: MockPublisher) -> None:
        self._publisher = publisher

    def pull(self, topic: str, subscription: str, max_messages: int = 1) -> list[Message]:
        key = f"{topic}/{subscription}"
        queue = self._publisher._subscriptions.get(key)
        if not queue:
            return []
        messages = []
        for _ in range(min(max_messages, len(queue))):
            msg = queue.popleft()
            msg.ack_id = f"ack-{time.time()}-{len(messages)}"
            messages.append(msg)
        return messages

    def acknowledge(self, msg: Message) -> None:
        print(f"Acknowledged: {msg.ack_id}")


if __name__ == "__main__":
    publisher = MockPublisher()
    subscriber = MockSubscriber(publisher)

    publisher.subscribe("orders", "order-processor")
    publisher.subscribe("orders", "order-audit")

    publisher.publish("orders", '{"order": 1}', source="api")
    publisher.publish("orders", '{"order": 2}', source="cron")

    pulled = subscriber.pull("orders", "order-processor", max_messages=2)
    print(f"Pulled {len(pulled)} messages:")
    for msg in pulled:
        print(f"  ID={msg.message_id} Data={msg.data} Attr={msg.attributes}")
        subscriber.acknowledge(msg)

    remaining = subscriber.pull("orders", "order-audit", max_messages=10)
    print(f"Audit subscription remaining: {len(remaining)} messages")

Output

stdout
Pulled 2 messages:
  ID=None Data={"order": 1} Attr={'source': 'api'}
Acknowledged: ack-...-0
  ID=None Data={"order": 2} Attr={'source': 'cron'}
Acknowledged: ack-...-1
Audit subscription remaining: 2 messages

How it works

The MockPublisher stores messages in per-subscription deques keyed by topic and subscription name, so publish broadcasts to all matching subscriptions while pull pops messages FIFO from a single subscription. Each pulled message gets a unique ack_id, and acknowledge prints confirmation — mimicking the real Pub/Sub lifecycle. This mock lets you test fan-out and consumer behavior deterministically without a real GCP emulator.

Common mistakes

  • Using a single queue for all subscriptions instead of keying by topic/subscription
  • Not generating unique ack IDs, causing ambiguous acknowledgments
  • Assuming publish is delivery-based rather than push-to-queue
  • Forgetting to set a unique message_id for traceability

Variations

  1. Add a `delay` parameter to simulate network latency
  2. Implement subscription filtering or message ordering by timestamp

Real-world use cases

  • Unit-testing event-driven services that read from a Pub/Sub subscription without spinning up an emulator.
  • Replaying or load-testing consumer logic locally so bugs surface before deployment.
  • Demonstrating fan-out to multiple subscribers in microservices workshops or CI demos.

Sponsored

Run this sample

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

Open editor

More from Cloud + Python

Related tutorials and quizzes for this topic.