Mocking the Cloud Spanner C++ Client with Google Mock

In this document we describe how to write unit tests that mock google::cloud::spanner::Client using Google Mock. This document assumes the reader is familiar with the Google Test and Google Mock frameworks and with the Cloud Spanner C++ Client.

First include the headers for the Cloud Spanner Client, the mocking class, and the Google Mock framework.

See more code actions.
#include "google/cloud/spanner/client.h"
#include "google/cloud/spanner/mocks/mock_spanner_connection.h"
#include "google/cloud/spanner/mocks/row.h"
#include <google/protobuf/text_format.h>
#include <gmock/gmock.h>

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

using ::testing::Return;
namespace spanner = ::google::cloud::spanner;

Create a mocking object for google::cloud::spanner::Connection:

  auto conn = std::make_shared<google::cloud::spanner_mocks::MockConnection>();

We will setup this mock in a second, but first let's look at how it is used to create a google::cloud::spanner::Client object:

  spanner::Client client(conn);

Once the client is created you can make calls on the client as usual:

  auto rows = client.ExecuteQuery(
      spanner
::SqlStatement("SELECT Id, Greeting FROM Greetings"));

And then verify the results meet your expectations:

  int count = 0;
 
using RowType = std::tuple<std::int64_t, std::string>;
 
for (auto const& row : spanner::StreamOf<RowType>(rows)) {
    ASSERT_TRUE
(row);
   
auto expected_id = ++count;
    EXPECT_EQ
(expected_id, std::get<0>(*row));
    EXPECT_EQ
("Hello World", std::get<1>(*row));
 
}

All of this depends on creating a google::cloud::spanner::RowStream that simulates the stream of results you want. To do so, you need to mock a source that streams google::cloud::spanner::Rows:

  auto source =
      std
::make_unique<google::cloud::spanner_mocks::MockResultSetSource>();

The source must define the names and types of the columns returned by the query:

  auto constexpr kText = R"pb(
    row_type
: {
      fields
: {
        name
: "Id",
        type
: { code: INT64 }
     
}
      fields
: {
        name
: "Greeting",
        type
: { code: STRING }
     
}
   
})pb";
  google
::spanner::v1::ResultSetMetadata metadata;
  ASSERT_TRUE
(google::protobuf::TextFormat::ParseFromString(kText, &metadata));
  EXPECT_CALL
(*source, Metadata()).WillRepeatedly(Return(metadata));

And then setup the mock to return the results. Note that the results are returned one value at a time, even if a row contains multiple values.

  EXPECT_CALL(*source, NextRow())
     
.WillOnce(Return(google::cloud::spanner_mocks::MakeRow(
         
{{"Id", spanner::Value(1)},
           
{"Greeting", spanner::Value("Hello World")}})))
     
.WillOnce(Return(google::cloud::spanner_mocks::MakeRow(
         
{{"Id", spanner::Value(2)},
           
{"Greeting", spanner::Value("Hello World")}})))

Note that the last value in the stream is indicated by an absl::optional without a value:

      .WillOnce(Return(spanner::Row()));

Once the source has been created and its behavior mocked, you mock the behavior for ExecuteQuery:

  EXPECT_CALL(*conn, ExecuteQuery)
     
.WillOnce([&source](spanner::Connection::SqlParams const&)
                   
-> spanner::RowStream {
       
return spanner::RowStream(std::move(source));
     
});

Full Listing

Finally we present the full code for this example:


#include "google/cloud/spanner/client.h"
#include "google/cloud/spanner/mocks/mock_spanner_connection.h"
#include "google/cloud/spanner/mocks/row.h"
#include <google/protobuf/text_format.h>
#include <gmock/gmock.h>

namespace {

using ::testing::Return;
namespace spanner = ::google::cloud::spanner;

TEST
(MockSpannerClient, SuccessfulExecuteQuery) {
 
// Create a mock object to stream the results of a ExecuteQuery.
 
auto source =
      std
::make_unique<google::cloud::spanner_mocks::MockResultSetSource>();

 
// Setup the return type of the ExecuteQuery results:
 
auto constexpr kText = R"pb(
    row_type
: {
      fields
: {
        name
: "Id",
        type
: { code: INT64 }
     
}
      fields
: {
        name
: "Greeting",
        type
: { code: STRING }
     
}
   
})pb";
  google
::spanner::v1::ResultSetMetadata metadata;
  ASSERT_TRUE
(google::protobuf::TextFormat::ParseFromString(kText, &metadata));
  EXPECT_CALL
(*source, Metadata()).WillRepeatedly(Return(metadata));

 
// Setup the mock source to return some values:
  EXPECT_CALL
(*source, NextRow())
     
.WillOnce(Return(google::cloud::spanner_mocks::MakeRow(
         
{{"Id", spanner::Value(1)},
           
{"Greeting", spanner::Value("Hello World")}})))
     
.WillOnce(Return(google::cloud::spanner_mocks::MakeRow(
         
{{"Id", spanner::Value(2)},
           
{"Greeting", spanner::Value("Hello World")}})))
     
.WillOnce(Return(spanner::Row()));

 
// Create a mock for `spanner::Connection`:
 
auto conn = std::make_shared<google::cloud::spanner_mocks::MockConnection>();

 
// Setup the connection mock to return the results previously setup:
  EXPECT_CALL
(*conn, ExecuteQuery)
     
.WillOnce([&source](spanner::Connection::SqlParams const&)
                   
-> spanner::RowStream {
       
return spanner::RowStream(std::move(source));
     
});

 
// Create a client with the mocked connection:
  spanner
::Client client(conn);

 
// Make the request and verify the expected results:
 
auto rows = client.ExecuteQuery(
      spanner
::SqlStatement("SELECT Id, Greeting FROM Greetings"));

 
int count = 0;
 
using RowType = std::tuple<std::int64_t, std::string>;
 
for (auto const& row : spanner::StreamOf<RowType>(rows)) {
    ASSERT_TRUE
(row);
   
auto expected_id = ++count;
    EXPECT_EQ
(expected_id, std::get<0>(*row));
    EXPECT_EQ
("Hello World", std::get<1>(*row));
 
}
}

}  // namespace