Skip to content

Commit

Permalink
doc(bigtable): how to mock the Data API (#9415)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbolduc authored Jul 2, 2022
1 parent 6a7c0a8 commit 90a24ec
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 2 deletions.
2 changes: 2 additions & 0 deletions google/cloud/bigtable/doc/bigtable-main.dox
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ which should give you a taste of the Cloud Bigtable C++ client library API.

- @ref bigtable-samples-grpc-credentials "Examples for using various gRPC Credential Classes with Cloud Bigtable C++ client"

- @ref bigtable-mocking "Mocking the Cloud Bigtable C++ client"

[read-more-about-gcp-bigtable]: https://cloud.google.com/bigtable/docs/ 'Read more about GCP Bigtable'
[read-instances-clusters]: https://cloud.google.com/bigtable/docs/instances-clusters-nodes 'Instances and Clusters'
[read-tables]: https://cloud.google.com/bigtable/docs/overview 'Tables'
Expand Down
53 changes: 53 additions & 0 deletions google/cloud/bigtable/doc/bigtable-mocking.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*!

@page bigtable-mocking Mocking the Cloud Bigtable C++ Client with Google Mock

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

## Mocking a successful `Table::ReadRows()`

First include the headers for the `Table`, the mocking classes, and the Google
Mock framework.

@snippet howto_mock_data_api.cc required-includes

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

@snippet howto_mock_data_api.cc helper-aliases

Create a mock connection:

@snippet howto_mock_data_api.cc create-mock

Now we are going to set expectations on this mock. For this test we will have it
return a `RowReader` that will successfully yield "r1" then "r2". A helper
function, `bigtable_mocks::MakeRowReader()` is provided for this purpose.

@snippet howto_mock_data_api.cc simulate-call

Create a table with the mocked connection:

@snippet howto_mock_data_api.cc create-table

Make the table call:

@snippet howto_mock_data_api.cc make-call

To verify the results, we loop over the rows returned by the `RowReader`:

@snippet howto_mock_data_api.cc verify-results

## Full Listing

Finally we present the full code for this example in the `ReadRowsSuccess` test.

We also provide `ReadRowsFailure` as an example for mocking an unsuccessful
`Table::ReadRows()` call, plus `AsyncReadRows` as an example for how one might
use the `DataConnection` to mock a `Table::AsyncReadRows()` call.

@snippet howto_mock_data_api.cc all

*/
5 changes: 3 additions & 2 deletions google/cloud/bigtable/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ if (BUILD_TESTING)
table_admin_iam_policy_snippets.cc
table_admin_snippets.cc)

set(bigtable_examples_unit_tests # cmake-format: sort
bigtable_examples_common_test.cc)
set(bigtable_examples_unit_tests
# cmake-format: sort
bigtable_examples_common_test.cc howto_mock_data_api.cc)

include(CreateBazelConfig)
export_list_to_bazel("bigtable_examples.bzl" "bigtable_examples" YEAR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@

bigtable_examples_unit_tests = [
"bigtable_examples_common_test.cc",
"howto_mock_data_api.cc",
]
121 changes: 121 additions & 0 deletions google/cloud/bigtable/examples/howto_mock_data_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! [all]

//! [required-includes]
#include "google/cloud/bigtable/mocks/mock_data_connection.h"
#include "google/cloud/bigtable/mocks/mock_row_reader.h"
#include "google/cloud/bigtable/table.h"
#include <gmock/gmock.h>
//! [required-includes]

namespace {

//! [helper-aliases]
using ::testing::ByMove;
using ::testing::ElementsAre;
using ::testing::Return;
namespace gc = ::google::cloud;
namespace cbt = ::google::cloud::bigtable;
namespace cbtm = ::google::cloud::bigtable_mocks;
//! [helper-aliases]

TEST(MockTableTest, ReadRowsSuccess) {
// Create a mock connection:
//! [create-mock]
auto mock = std::make_shared<cbtm::MockDataConnection>();
//! [create-mock]

// Set up our mock connection to return a `RowReader` that will successfully
// yield "r1" then "r2":
//! [simulate-call]
std::vector<cbt::Row> rows = {cbt::Row("r1", {}), cbt::Row("r2", {})};
EXPECT_CALL(*mock, ReadRows)
.WillOnce(Return(ByMove(cbtm::MakeRowReader(rows))));
//! [simulate-call]

// Create a table with the mocked connection:
//! [create-table]
cbt::Table table(mock, cbt::TableResource("project", "instance", "table"));
//! [create-table]

// Make the table call:
//! [make-call]
auto reader = table.ReadRows(cbt::RowSet(), cbt::Filter::PassAllFilter());
//! [make-call]

// Loop over the rows returned by the `RowReader` and verify the results:
//! [verify-results]
std::vector<std::string> row_keys;
for (gc::StatusOr<cbt::Row> const& row : reader) {
ASSERT_TRUE(row.ok());
row_keys.push_back(row->row_key());
}
EXPECT_THAT(row_keys, ElementsAre("r1", "r2"));
//! [verify-results]
}

TEST(MockTableTest, ReadRowsFailure) {
auto mock = std::make_shared<cbtm::MockDataConnection>();

// Return a `RowReader` that yields only a failing status (no rows).
gc::Status final_status(gc::StatusCode::kPermissionDenied, "fail");
EXPECT_CALL(*mock, ReadRows)
.WillOnce(Return(ByMove(cbtm::MakeRowReader({}, final_status))));

cbt::Table table(mock, cbt::TableResource("project", "instance", "table"));
cbt::RowReader reader =
table.ReadRows(cbt::RowSet(), cbt::Filter::PassAllFilter());

// In this test, we expect one `StatusOr<Row>`, that holds a bad status.
auto it = reader.begin();
ASSERT_NE(it, reader.end());
EXPECT_FALSE((*it).ok());
ASSERT_EQ(++it, reader.end());
}

TEST(TableTest, AsyncReadRows) {
// Let's use an alias to ignore fields we don't care about.
using ::testing::Unused;

// Create a mock connection, and set its expectations.
auto mock = std::make_shared<cbtm::MockDataConnection>();
EXPECT_CALL(*mock, AsyncReadRows)
.WillOnce([](Unused, auto const& on_row, auto const& on_finish, Unused,
Unused, Unused) {
// Simulate returning two rows, "r1" and "r2", by invoking the `on_row`
// callback. Verify the values of the returned `future<bool>`s.
EXPECT_TRUE(on_row(cbt::Row("r1", {})).get());
EXPECT_TRUE(on_row(cbt::Row("r2", {})).get());
// Simulate a stream that ends successfully.
on_finish(gc::Status());
});

// Create the table with a mocked connection.
cbt::Table table(mock, cbt::TableResource("project", "instance", "table"));

// These are example callbacks for demonstration purposes. Applications should
// likely invoke their own callbacks when testing.
auto on_row = [](cbt::Row const&) { return gc::make_ready_future(true); };
auto on_finish = [](gc::Status const&) {};

// Make the client cal.
table.AsyncReadRows(on_row, on_finish, cbt::RowSet(),
cbt::Filter::PassAllFilter());
}

} // namespace

//! [all]l
2 changes: 2 additions & 0 deletions google/cloud/bigtable/mocks/mock_data_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
* including errors from a `bigtable::Table`. To do so, construct a
* `bigtable::Table` with an instance of this class. Then use the Google Test
* framework functions to program the behavior of this mock.
*
* See @ref bigtable-mocking for a complete example that mocks `Table` calls.
*/
class MockDataConnection : public bigtable::DataConnection {
public:
Expand Down

0 comments on commit 90a24ec

Please sign in to comment.