Testing your Cloud Pub/Sub subscriber application with googlemock

This document describes how to test your own Cloud Pub/Sub application using the Cloud Pub/Sub C++ client library, Google Test and the and Google Test Mocking Framework.

Mocking a Successful Subscriber::Subscribe() call

First include the headers for the Cloud Pub/Sub Subscriber Client, the mocking class, and the Google Mock framework.

#include "google/cloud/pubsub/mocks/mock_publisher_connection.h"
#include "google/cloud/pubsub/publisher.h"
#include <gmock/gmock.h>

The example uses a number of aliases to save typing and improve readability:

using ::google::cloud::promise;
using ::google::cloud::pubsub_mocks::MockAckHandler;
using ::google::cloud::pubsub_mocks::MockSubscriberConnection;
using ::testing::Return;
using ::testing::UnorderedElementsAre;
namespace pubsub = ::google::cloud::pubsub;

Create a mocking object for google::cloud::pubsub::SubscriberConnection:

  auto mock = std::make_shared<MockSubscriberConnection>();

It is customary to first setup the expectations for your mock, and then write the rest of the code. In this case we need to create a mock pubsub::AckHandler and setup its expectation for each simulated message:

          auto mock_handler = std::make_unique<MockAckHandler>();
          EXPECT_CALL
(*mock_handler, ack_id)
             
.WillRepeatedly(Return("ack-id-" + std::to_string(i)));
          EXPECT_CALL
(*mock_handler, ack).Times(1);

To simulate the background generation of messages we use a lambda:

  auto generator =
     
[](promise<google::cloud::Status> promise,
         pubsub
::SubscriberConnection::SubscribeParams const& params) {
       
for (int i = 0; i != 3; ++i) {
         
auto mock_handler = std::make_unique<MockAckHandler>();
          EXPECT_CALL
(*mock_handler, ack_id)
             
.WillRepeatedly(Return("ack-id-" + std::to_string(i)));
          EXPECT_CALL
(*mock_handler, ack).Times(1);

          params
.callback(pubsub::MessageBuilder{}
                             
.SetData("message-" + std::to_string(i))
                             
.Build(),
                          pubsub
::AckHandler(std::move(mock_handler)));
       
}
       
// Close the stream with a successful error code
        promise
.set_value({});
     
};

This message generator is used in the SubscriberConnection expectations:

  EXPECT_CALL(*mock, Subscribe)
     
.WillOnce([&](pubsub::SubscriberConnection::SubscribeParams params) {
        promise
<google::cloud::Status> p;
       
auto result = p.get_future();
       
// start the generator in a separate thread.
       
(void)std::async(std::launch::async, generator, std::move(p),
                         std
::move(params));
       
return result;
     
});

With the expectations in place, create a google::cloud::pubsub::Subscriber object:

  pubsub::Subscriber subscriber(mock);

And then make calls on this client as usual:

  std::vector<pubsub::PubsubMessageDataType> payloads;
 
auto handler = [&](pubsub::Message const& m, pubsub::AckHandler h) {
    payloads
.push_back(m.data());
    std
::move(h).ack();
 
};
 
auto session = subscriber.Subscribe(handler);

And then verify the results meet your expectations:

  EXPECT_TRUE(session.get().ok());
  EXPECT_THAT
(payloads,
             
UnorderedElementsAre("message-0", "message-1", "message-2"));

Full Listing

Finally we present the full code for this example:

#include "google/cloud/pubsub/mocks/mock_ack_handler.h"
#include "google/cloud/pubsub/mocks/mock_exactly_once_ack_handler.h"
#include "google/cloud/pubsub/mocks/mock_subscriber_connection.h"
#include "google/cloud/pubsub/subscriber.h"
#include <gmock/gmock.h>
#include <future>

namespace {

using ::google::cloud::promise;
using ::google::cloud::pubsub_mocks::MockAckHandler;
using ::google::cloud::pubsub_mocks::MockSubscriberConnection;
using ::testing::Return;
using ::testing::UnorderedElementsAre;
namespace pubsub = ::google::cloud::pubsub;

TEST
(MockSubscribeExample, Subscribe) {
 
auto mock = std::make_shared<MockSubscriberConnection>();

 
// Generate 3 messages in a separate thread and then close the
 
// subscription with success.
 
auto generator =
     
[](promise<google::cloud::Status> promise,
         pubsub
::SubscriberConnection::SubscribeParams const& params) {
       
for (int i = 0; i != 3; ++i) {
         
auto mock_handler = std::make_unique<MockAckHandler>();
          EXPECT_CALL
(*mock_handler, ack_id)
             
.WillRepeatedly(Return("ack-id-" + std::to_string(i)));
          EXPECT_CALL
(*mock_handler, ack).Times(1);

          params
.callback(pubsub::MessageBuilder{}
                             
.SetData("message-" + std::to_string(i))
                             
.Build(),
                          pubsub
::AckHandler(std::move(mock_handler)));
       
}
       
// Close the stream with a successful error code
        promise
.set_value({});
     
};

  EXPECT_CALL
(*mock, Subscribe)
     
.WillOnce([&](pubsub::SubscriberConnection::SubscribeParams params) {
        promise
<google::cloud::Status> p;
       
auto result = p.get_future();
       
// start the generator in a separate thread.
       
(void)std::async(std::launch::async, generator, std::move(p),
                         std
::move(params));
       
return result;
     
});

  pubsub
::Subscriber subscriber(mock);

  std
::vector<pubsub::PubsubMessageDataType> payloads;
 
auto handler = [&](pubsub::Message const& m, pubsub::AckHandler h) {
    payloads
.push_back(m.data());
    std
::move(h).ack();
 
};
 
auto session = subscriber.Subscribe(handler);

  EXPECT_TRUE
(session.get().ok());
  EXPECT_THAT
(payloads,
             
UnorderedElementsAre("message-0", "message-1", "message-2"));
}

}  // namespace

Mocking with Exactly-Once Delivery

To mock exactly once delivery is very similar:

using ::google::cloud::Status;
using ::google::cloud::pubsub_mocks::MockExactlyOnceAckHandler;
using ::testing::ByMove;

TEST
(MockSubscribeExample, ExactlyOnceSubscribe) {
 
auto mock = std::make_shared<MockSubscriberConnection>();

 
// Generate 3 messages in a separate thread and then close the
 
// subscription with success.
 
auto generator =
     
[](promise<google::cloud::Status> promise,
         pubsub
::SubscriberConnection::ExactlyOnceSubscribeParams const&
             params
) {
       
for (int i = 0; i != 3; ++i) {
         
auto mock_handler = std::make_unique<MockExactlyOnceAckHandler>();
          EXPECT_CALL
(*mock_handler, ack_id)
             
.WillRepeatedly(Return("ack-id-" + std::to_string(i)));
          EXPECT_CALL
(*mock_handler, ack)
             
.WillOnce(Return(ByMove(make_ready_future(Status{}))));

         
// Simulate callbacks
          params
.callback(
              pubsub
::MessageBuilder{}
                 
.SetData("message-" + std::to_string(i))
                 
.Build(),
              pubsub
::ExactlyOnceAckHandler(std::move(mock_handler)));
       
}
       
// Close the stream with a successful error code
        promise
.set_value({});
     
};

  EXPECT_CALL
(*mock, ExactlyOnceSubscribe)
     
.WillOnce(
         
[&](pubsub::SubscriberConnection::ExactlyOnceSubscribeParams params) {
            promise
<google::cloud::Status> p;
           
auto result = p.get_future();
           
// start the generator in a separate thread.
           
(void)std::async(std::launch::async, generator, std::move(p),
                             std
::move(params));
           
return result;
         
});

  pubsub
::Subscriber subscriber(mock);

  std
::vector<pubsub::PubsubMessageDataType> payloads;
 
auto callback = [&](pubsub::Message const& m,
                      pubsub
::ExactlyOnceAckHandler h) {
    payloads
.push_back(m.data());
    std
::move(h).ack();
 
};
 
auto session = subscriber.Subscribe(callback);

  EXPECT_TRUE
(session.get().ok());
  EXPECT_THAT
(payloads,
             
UnorderedElementsAre("message-0", "message-1", "message-2"));
}