Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc(bigtable): how to mock the Data API #9415

Merged
merged 1 commit into from
Jul 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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