preloader
🗓️ January 12, 2026 | 5

Testing Google Pub/Sub Locally? Microcks Now Supports the Emulator!

At Dojo, speed is critical. Not just in how fast our card machines process payments (which is milliseconds, by the way 😉), but in how fast we ship value to our customers.

Our platform relies heavily on an event-driven architecture running on Google Cloud. While this is powerful in production, it creates friction for our “inner loop” development. Developers often have to connect to real cloud resources just to test a single microservice, which means managing complex credentials, spending time waiting for cloud resources to deploy, and incurring cloud costs for every test run.

image

We want to shift our testing left, enabling our engineers to spin up a transaction flow simulation on their laptop, completely offline. We want to publish mock payment events, verify how services consume them, and iterate instantly without waiting for a cloud deployment.

However, Microcks couldn’t be used with the Google Pub/Sub emulator, so I’ve recently contributed a new feature to Microcks to bridge this gap.

What is the use case?

The use case is simple: Local Development without the Cloud.

Developers often use tools like LocalStack to mock AWS services (like SQS or SNS) locally. We wanted the same seamless experience for Google Pub/Sub.

Previously, Microcks’ Google Pub/Sub implementation assumed it was always talking to the real GCP service. It required a Service Account key file and valid authentication to a real Google Cloud project. This is great for integration testing in a staging environment, but for a developer working offline or trying to keep their setup lightweight, it was a blocker.

With this change (introduced in PR #1851, available from v1.13.1), Microcks can now detect and connect to a local Google Pub/Sub emulator. This allows you to publish mock messages from Microcks directly to the emulator.

Example Use Case

Let’s look at how to set this up. The magic happens by configuring the Microcks Async Minion to recognise the standard PUBSUB_EMULATOR_HOST environment variable.

1. The Setup

First, you need a docker-compose.yml that spins up Microcks (and its Async Minion) alongside the Google Pub/Sub emulator.

Here is the Docker Compose configuration you can use to get started:

services:
  microcks:
    image: quay.io/microcks/microcks-uber:1.13.1
    ports:
      - "8080:8080"
    environment:
      - ASYNC_MINION_URL=http://microcks-async-minion:8081 # Link to Async Minion
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
      interval: 10s
      timeout: 3s

  microcks-async-minion:
    image: quay.io/microcks/microcks-uber-async-minion:1.13.1
    ports:
      - "8081:8081"
    environment:
      - ASYNC_PROTOCOLS=,GOOGLEPUBSUB # Enable Google Pub/Sub support
      - MICROCKS_HOST_PORT=microcks:8080 # Link to Microcks
      - PUBSUB_EMULATOR_HOST=pubsub-emulator:8681 # Link to Pub/Sub Emulator
    depends_on:
      microcks:
        condition: service_healthy
      pubsub-emulator:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8081/q/health/ready"]
      interval: 10s
      timeout: 3s

  pubsub-emulator:
    image: gcr.io/google.com/cloudsdktool/cloud-sdk:441.0.0-emulators
    command: gcloud beta emulators pubsub start --host-port=0.0.0.0:8681 --project=my-project
    ports:
      - "8681:8681"

Running this with docker compose up should result in a local Microcks environment that you can access the Web UI at http://localhost:8080

2. The AsyncAPI Specification

You don’t need to change your AsyncAPI specification to support this! That’s the beauty of it. Microcks uses the same contract definition whether you are targeting production GCP or your local emulator.

Here is the AsyncAPI file we are using for this example:

asyncapi: 3.0.0
id: 'urn:io.microcks.example.hello-microcks'
info:
  title: Hello Microcks API
  version: 1.0.0
  description: Example showing a simple pub/sub message
defaultContentType: application/json
channels:
  helloEvents:
    address: microcks/hello
    description: Topic with Hello events
    bindings:
      googlepubsub:
        schemaSettings:
          encoding: json
          name: projects/my-project/topics/my-topic
    messages:
      hello.message:
        description: A simple Hello event message
        traits:
          - $ref: '#/components/messageTraits/commonHeaders'
        payload:
          type: object
          properties:
            greeting:
              type: string
              const: hello
            fullName:
              type: string
            sentAt:
              type: string
        examples:
          - name: microcks-example
            summary: Hello to Microcks example message
            payload:
              greeting: hello
              fullName: Microcks
              sentAt: '{{now()}}'
            headers:
              version: 1
          - name: random-example
            summary: Example message with random name
            payload:
              greeting: hello
              fullName: '{{randomFirstName()}}'
              sentAt: '{{now()}}'
            headers:
              version: 2

operations:
  receivedHellos:
    action: receive
    channel:
      $ref: '#/channels/helloEvents'
    summary: Receive hellos
    messages:
      - $ref: '#/channels/helloEvents/messages/hello.message'
components:
  messageTraits:
    commonHeaders:
      headers:
        type: object
        properties:
          version:
            type: integer
            minimum: 0
            maximum: 10

This spec has two example messages, one with the fullName field set to “Microcks” and another with a random name for each message.

How you can verify it’s working

Once you have your stack running, verification is straightforward.

Import your API

Import the AsyncAPI definition above into Microcks using the Web UI uploader at http://localhost:8080/#/importers

Check the logs

If you look at the logs for the microcks-async-minion container, you should see that it has detected the emulator configuration and is outputting mock message.

...snip...
[io.git.mic.min.asy.pro.GooglePubSubProducerManager] (QuarkusQuartzScheduler_Worker-9) Using Google PubSub emulator at pubsub-emulator:8681
[io.git.mic.min.asy.pro.GooglePubSubProducerManager] (QuarkusQuartzScheduler_Worker-9) Publishing on topic {HelloMicrocksAPI-1.0.0-receivedHellos}, message: {"greeting":"hello","fullName":"Microcks","sentAt":"1767870113823"}
...snip...

Receiving Messages

You can use a local subscriber script to confirm that messages are arriving.

Here is a simple python script that streams messages from the topic

from google.cloud import pubsub_v1
from google.api_core.exceptions import AlreadyExists, NotFound

PROJECT_ID = "my-project"
TOPIC_ID = "HelloMicrocksAPI-1.0.0-receivedHellos"
SUBSCRIPTION_ID = "stream-out-py"

def get_or_create_subscription(project_id, topic_id, subscription_id):
    """
    Creates a subscription if it doesn't exist, then returns the path.
    """

    subscriber = pubsub_v1.SubscriberClient()
    publisher = pubsub_v1.PublisherClient()

    topic_path = publisher.topic_path(project_id, topic_id)
    subscription_path = subscriber.subscription_path(project_id, subscription_id)

    with subscriber:
        try:
            subscriber.create_subscription(
                request={"name": subscription_path, "topic": topic_path},
            )
            print(f"Created new subscription: {subscription_path}")
        except AlreadyExists:
            print(f"Subscription already exists: {subscription_path}")
        except NotFound:
            print(f"Error: The Topic '{topic_id}' does not exist. Please create the topic first.")
            raise

    return subscription_path

def receive_messages(sub_path):
    """Receives messages from the subscription."""

    def callback(message):
        print(f"Received: {message.data.decode('utf-8')}")
        message.ack()

    subscriber = pubsub_v1.SubscriberClient()
    stream = subscriber.subscribe(sub_path, callback=callback)
    print(f"Listening for messages...\n")

    with subscriber:
        stream.result()

if __name__ == "__main__":
    sub_path = get_or_create_subscription(PROJECT_ID, TOPIC_ID, SUBSCRIPTION_ID)
    receive_messages(sub_path)

Running this with the environment variable PUBSUB_EMULATOR_HOST pointing at our emulator

PUBSUB_EMULATOR_HOST=localhost:8681 python stream.py

Will output live message events

Created new subscription: projects/my-project/subscriptions/stream-out-py
Listening for messages...

Received: {"greeting":"hello","fullName":"Microcks","sentAt":"1767870122753"}
Received: {"greeting":"hello","fullName":"Josie","sentAt":"1767870122759"}
Received: {"greeting":"hello","fullName":"Microcks","sentAt":"1767870125757"}
Received: {"greeting":"hello","fullName":"Merlin","sentAt":"1767870125762"}

🥳

Summary

This small but mighty change opens up a smoother local development workflow for anyone building event-driven systems with Google Cloud. For us at Dojo, it means faster iterations and happier developers. By supporting the native Pub/Sub emulator, Microcks continues to be the most versatile tool for mocking and testing, no matter where your infrastructure lives, in the cloud or on your machine.

I hope this helps you streamline your testing loops! If you have any questions or feedback, feel free to reach out on GitHub.

Happy Mocking! 🚀

Adam Hicks

Adam Hicks

Staff Software Engineer at Dojo

comments powered by Disqus