Testing with the Pub/Sub emulator

You can test functions locally before you deploy them, by using Functions Framework in conjunction with the Pub/Sub emulator.

Use Functions Framework with Pub/Sub emulator

You can trigger a function locally using a push message from the Pub/Sub emulator.

Test this feature as described below. Note that it requires you to use three separate terminal instances:

  1. Make sure you have the pack tool and Docker installed. You can learn more about Buildpacks and Cloud Functions on the Buildpacks page.

  2. In the first terminal, start the Pub/Sub emulator on port 8043 in a local project:

    gcloud beta emulators pubsub start \
        --project=abc \
        --host-port='localhost:8043'
    
  3. In the second terminal, create a Pub/Sub topic and subscription:

    curl -s -X PUT 'http://localhost:8043/v1/projects/abc/topics/mytopic'
    

    Use http://localhost:8080 as the push subscription's endpoint.

    curl -s -X PUT 'http://localhost:8043/v1/projects/abc/subscriptions/mysub' \
        -H 'Content-Type: application/json' \
        --data '{"topic":"projects/abc/topics/mytopic","pushConfig":{"pushEndpoint":"http://localhost:8080/projects/abc/topics/mytopic"}}'
    
  4. In the third terminal, clone the sample repository to your local machine:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Alternatively, you can download the sample as a zip file and extract it.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    Alternatively, you can download the sample as a zip file and extract it.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    Alternatively, you can download the sample as a zip file and extract it.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    Alternatively, you can download the sample as a zip file and extract it.

    Change to the directory that contains the Cloud Functions sample code:

    Node.js

    cd nodejs-docs-samples/functions/helloworld/

    Python

    cd python-docs-samples/functions/helloworld/

    Go

    cd golang-samples/functions/helloworld/

    Java

    cd java-docs-samples/functions/helloworld/hello-pubsub/

    Take a look at the sample code:

    Node.js

    /**
     * Background Cloud Function to be triggered by Pub/Sub.
     * This function is exported by index.js, and executed when
     * the trigger topic receives a message.
     *
     * @param {object} message The Pub/Sub message.
     * @param {object} context The event metadata.
     */
    exports.helloPubSub = (message, context) => {
      const name = message.data
        ? Buffer.from(message.data, 'base64').toString()
        : 'World';
    
      console.log(`Hello, ${name}!`);
    };

    Python

    def hello_pubsub(event, context):
        """Background Cloud Function to be triggered by Pub/Sub.
        Args:
             event (dict):  The dictionary with data specific to this type of
                            event. The `@type` field maps to
                             `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
                            The `data` field maps to the PubsubMessage data
                            in a base64-encoded string. The `attributes` field maps
                            to the PubsubMessage attributes if any is present.
             context (google.cloud.functions.Context): Metadata of triggering event
                            including `event_id` which maps to the PubsubMessage
                            messageId, `timestamp` which maps to the PubsubMessage
                            publishTime, `event_type` which maps to
                            `google.pubsub.topic.publish`, and `resource` which is
                            a dictionary that describes the service API endpoint
                            pubsub.googleapis.com, the triggering topic's name, and
                            the triggering event type
                            `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
        Returns:
            None. The output is written to Cloud Logging.
        """
        import base64
    
        print("""This Function was triggered by messageId {} published at {} to {}
        """.format(context.event_id, context.timestamp, context.resource["name"]))
    
        if 'data' in event:
            name = base64.b64decode(event['data']).decode('utf-8')
        else:
            name = 'World'
        print(f'Hello {name}!')

    Go

    
    // Package helloworld provides a set of Cloud Functions samples.
    package helloworld
    
    import (
    	"context"
    	"log"
    )
    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See the documentation for more details:
    // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
    type PubSubMessage struct {
    	Data []byte `json:"data"`
    }
    
    // HelloPubSub consumes a Pub/Sub message.
    func HelloPubSub(ctx context.Context, m PubSubMessage) error {
    	name := string(m.Data) // Automatically decoded from base64.
    	if name == "" {
    		name = "World"
    	}
    	log.Printf("Hello, %s!", name)
    	return nil
    }
    

    Java

    
    import com.google.cloud.functions.BackgroundFunction;
    import com.google.cloud.functions.Context;
    import functions.eventpojos.PubsubMessage;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    public class HelloPubSub implements BackgroundFunction<PubsubMessage> {
      private static final Logger logger = Logger.getLogger(HelloPubSub.class.getName());
    
      @Override
      public void accept(PubsubMessage message, Context context) {
        String name = "world";
        if (message != null && message.getData() != null) {
          name = new String(
              Base64.getDecoder().decode(message.getData().getBytes(StandardCharsets.UTF_8)),
              StandardCharsets.UTF_8);
        }
        logger.info(String.format("Hello %s!", name));
        return;
      }
    }

    Create the buildpack (this may take a few minutes):

    Node.js

    pack build \
      --builder gcr.io/buildpacks/builder:v1 \
      --env GOOGLE_FUNCTION_SIGNATURE_TYPE=event \
      --env GOOGLE_FUNCTION_TARGET=helloPubSub \
      my-function
    

    Python

    pack build \
      --builder gcr.io/buildpacks/builder:v1 \
      --env GOOGLE_FUNCTION_SIGNATURE_TYPE=event \
      --env GOOGLE_FUNCTION_TARGET=hello_pubsub \
      my-function
    

    Go

    pack build \
      --builder gcr.io/buildpacks/builder:v1 \
      --env GOOGLE_FUNCTION_SIGNATURE_TYPE=event \
      --env GOOGLE_FUNCTION_TARGET=HelloPubSub \
      my-function
    

    Java

    pack build \
      --builder gcr.io/buildpacks/builder:v1 \
      --env GOOGLE_FUNCTION_SIGNATURE_TYPE=event \
      --env GOOGLE_FUNCTION_TARGET=functions.HelloPubSub \
      my-function
    

    Start the Pub/Sub function on port 8080. This is where the emulator will send push messages:

    docker run --rm -p 8080:8080 my-function
    
  5. In the second terminal, invoke the function by publishing a message. The message data needs to be encoded in base64. The example below uses the base64 encoded string {"foo":"bar"}.

    curl -s -X POST 'http://localhost:8043/v1/projects/abc/topics/mytopic:publish' \
        -H 'Content-Type: application/json' \
        --data '{"messages":[{"data":"eyJmb28iOiJiYXIifQ=="}]}'
    

    You should see the function output in the third terminal.

    Press Ctrl+C to abort.