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")); }