Skip to content

Commit

Permalink
Add support for std::function in MockFunction (#2277)
Browse files Browse the repository at this point in the history
  • Loading branch information
adambadura committed Aug 29, 2019
1 parent 565f1b8 commit b536866
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 41 deletions.
14 changes: 14 additions & 0 deletions googlemock/docs/cheat_sheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,20 @@ class MockFunction<R(A1, ..., An)> {
See this [recipe](cook_book.md#using-check-points) for one application of it.
## Callback Signature ##
When using `MockFunction<T>` as a mock for a callback the signature often becomes an issue. When the callback type is provided like this:
```cpp
using my_callback = std::function<bool(int)>;
```
there is no easy way to obtain the function signature (`bool(int)`) from the `my_callback` type.

`MockFunction` avoids this problem by using indirection of `SignatureOf` meta-function. The `MockFunction` retrieves function signature type by providing its `F` argument to the `SignatureOf` meta-function. While the `SignatureOf` recognizes `std::function<T>` and returns the `T` as the proper function signature. (Obviously, the `SignatureOf` is an identity function for the function signature itself!)

Thanks to this approach, with the example above, `MockFunction<my_callback>` can be used directly instead of having to write `MockFunction<bool(int)>`.

Note that `SignatureOf` is an extension point. It can be specialized for user types (for example `boost::function`) to have them supported by `MockFunction` as well.

### Flags

<!-- mdformat off(no multiline tables) -->
Expand Down
124 changes: 83 additions & 41 deletions googlemock/include/gmock/gmock-spec-builders.h
Original file line number Diff line number Diff line change
Expand Up @@ -1791,10 +1791,72 @@ void ReportUninterestingCall(CallReaction reaction, const std::string& msg);

} // namespace internal

// A MockFunction<F> class has one mock method whose type is F. It is
// useful when you just want your test code to emit some messages and
// have Google Mock verify the right messages are sent (and perhaps at
// the right times). For example, if you are exercising code:
// The SignatureOf<F> struct is a meta-function returning function
// signature corresponding to the provided F argument. It makes use of
// MockFunction easier by allowing it to accept more F arguments than
// just function signatures. Specializations provided here cover only
// a signature type itself and std::function. If a user wishes to use
// SignatureOf with other types (like for example boost::function)
// a corresponding specialization must be provided.
template <typename F>
struct SignatureOf;

template <typename R, typename... Args>
struct SignatureOf<R(Args...)> {
using type = R(Args...);
};

template <typename F>
struct SignatureOf<std::function<F>> : SignatureOf<F> {};

template <typename F>
using SignatureOfT = typename SignatureOf<F>::type;

namespace internal {

template <typename F>
class MockFunction;

template <typename R, typename... Args>
class MockFunction<R(Args...)> {
public:
MockFunction() {};
MockFunction(const MockFunction&) = delete;
MockFunction& operator=(const MockFunction&) = delete;

std::function<R(Args...)> AsStdFunction() {
return [this](Args... args) -> R {
return this->Call(std::forward<Args>(args)...);
};
}

// Implementation detail: the expansion of the MOCK_METHOD macro.
R Call(Args... args) {
mock_.SetOwnerAndName(this, "Call");
return mock_.Invoke(std::forward<Args>(args)...);
}

MockSpec<R(Args...)> gmock_Call(Matcher<Args>... m) {
mock_.RegisterOwner(this);
return mock_.With(std::move(m)...);
}

MockSpec<R(Args...)> gmock_Call(const WithoutMatchers&,
R (*)(Args...)) {
return this->gmock_Call(::testing::A<Args>()...);
}

private:
FunctionMocker<R(Args...)> mock_;
};

} // namespace internal

// A MockFunction<F> class has one mock method whose type is
// SignatureOfT<F>. It is useful when you just want your test code to
// emit some messages and have Google Mock verify the right messages
// are sent (and perhaps at the right times). For example, if you are
// exercising code:
//
// Foo(1);
// Foo(2);
Expand Down Expand Up @@ -1828,50 +1890,30 @@ void ReportUninterestingCall(CallReaction reaction, const std::string& msg);
// Bar("a") is called by which call to Foo().
//
// MockFunction<F> can also be used to exercise code that accepts
// std::function<F> callbacks. To do so, use AsStdFunction() method
// to create std::function proxy forwarding to original object's Call.
// Example:
// std::function<SignatureOfT<F>> callbacks. To do so, use
// AsStdFunction() method to create std::function proxy forwarding to
// original object's Call. Example:
//
// TEST(FooTest, RunsCallbackWithBarArgument) {
// MockFunction<int(string)> callback;
// EXPECT_CALL(callback, Call("bar")).WillOnce(Return(1));
// Foo(callback.AsStdFunction());
// }
//
// The SignatureOfT<F> indirection allows to use other types than just
// function signature type. This is typically useful when providing
// a mock for a predefined std::function type. Example:
//
// using predicate = std::function<bool(string)>;
// void MyFilterAlgorithm(predicate pred);
//
// TEST(FooTest, PredicateAlwaysAccepts) {
// MockFunction<predicate> pred_mock;
// EXPECT_CALL(pred_mock, Call(_)).WillRepeatedly(Return(true));
// MyFilterAlgorithm(pred_mock.AsStdFunction());
// }
template <typename F>
class MockFunction;

template <typename R, typename... Args>
class MockFunction<R(Args...)> {
public:
MockFunction() {}
MockFunction(const MockFunction&) = delete;
MockFunction& operator=(const MockFunction&) = delete;

std::function<R(Args...)> AsStdFunction() {
return [this](Args... args) -> R {
return this->Call(std::forward<Args>(args)...);
};
}

// Implementation detail: the expansion of the MOCK_METHOD macro.
R Call(Args... args) {
mock_.SetOwnerAndName(this, "Call");
return mock_.Invoke(std::forward<Args>(args)...);
}

internal::MockSpec<R(Args...)> gmock_Call(Matcher<Args>... m) {
mock_.RegisterOwner(this);
return mock_.With(std::move(m)...);
}

internal::MockSpec<R(Args...)> gmock_Call(const internal::WithoutMatchers&,
R (*)(Args...)) {
return this->gmock_Call(::testing::A<Args>()...);
}

private:
internal::FunctionMocker<R(Args...)> mock_;
};
using MockFunction = internal::MockFunction<SignatureOfT<F>>;

// The style guide prohibits "using" statements in a namespace scope
// inside a header file. However, the MockSpec class template is
Expand Down
39 changes: 39 additions & 0 deletions googlemock/test/gmock-function-mocker_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
# include <objbase.h>
#endif // GTEST_OS_WINDOWS

#include <functional>
#include <map>
#include <string>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"

Expand Down Expand Up @@ -558,6 +560,43 @@ TEST(MockMethodOverloadedMockMethodTest, CanOverloadOnConstnessInMacroBody) {
EXPECT_EQ(3, const_mock->Overloaded(1));
}

template <typename Signature>
class MockMethodMockFunctionSignatureTest : public ::testing::Test {
};

using SignatureTypes = ::testing::Types<
void(),
int(),
void(int),
int(bool, int),
int(bool, char, int, int, int, int, int, char, int, bool)
>;
TYPED_TEST_SUITE(MockMethodMockFunctionSignatureTest, SignatureTypes);

TYPED_TEST(MockMethodMockFunctionSignatureTest, SignatureOfTIsIdentityForSignature) {
using signature = TypeParam;
using expected = signature;
using actual = SignatureOfT<signature>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TYPED_TEST(MockMethodMockFunctionSignatureTest, SignatureOfTReturnsSignatureForStdFunction) {
using signature = TypeParam;
using expected = signature;
using actual = SignatureOfT<std::function<signature>>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TYPED_TEST(MockMethodMockFunctionSignatureTest, MockFunctionOfSignatureIsTheSameTypeAsMockFunctionOfStdFunction) {
using signature = TypeParam;
using expected = MockFunction<signature>;
using actual = MockFunction<std::function<signature>>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TEST(MockMethodMockFunctionTest, WorksForVoidNullary) {
MockFunction<void()> foo;
EXPECT_CALL(foo, Call());
Expand Down
39 changes: 39 additions & 0 deletions googlemock/test/gmock-generated-function-mockers_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@
# include <objbase.h>
#endif // GTEST_OS_WINDOWS

#include <functional>
#include <map>
#include <string>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"

Expand Down Expand Up @@ -557,6 +559,43 @@ TEST(OverloadedMockMethodTest, CanOverloadOnConstnessInMacroBody) {
EXPECT_EQ(3, const_mock->Overloaded(1));
}

template <typename Signature>
class MockFunctionSignatureTest : public ::testing::Test {
};

using SignatureTypes = ::testing::Types<
void(),
int(),
void(int),
int(bool, int),
int(bool, char, int, int, int, int, int, char, int, bool)
>;
TYPED_TEST_SUITE(MockFunctionSignatureTest, SignatureTypes);

TYPED_TEST(MockFunctionSignatureTest, SignatureOfTIsIdentityForSignature) {
using signature = TypeParam;
using expected = signature;
using actual = SignatureOfT<signature>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TYPED_TEST(MockFunctionSignatureTest, SignatureOfTReturnsSignatureForStdFunction) {
using signature = TypeParam;
using expected = signature;
using actual = SignatureOfT<std::function<signature>>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TYPED_TEST(MockFunctionSignatureTest, MockFunctionOfSignatureIsTheSameTypeAsMockFunctionOfStdFunction) {
using signature = TypeParam;
using expected = MockFunction<signature>;
using actual = MockFunction<std::function<signature>>;
constexpr auto is_same = std::is_same<actual, expected>::value;
EXPECT_TRUE(is_same);
}

TEST(MockFunctionTest, WorksForVoidNullary) {
MockFunction<void()> foo;
EXPECT_CALL(foo, Call());
Expand Down

0 comments on commit b536866

Please sign in to comment.