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.
Mocking a successful ExecuteQuery
First include the headers for the Cloud Spanner Client, the mocking class, and the Google Mock framework.
#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::Row
s:
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