googletest
googletest copied to clipboard
Add support for movable mocks
Does the feature exist in the most recent commit?
No
Why do we need this feature?
With the introduction of concepts and CTAD in C++, together with the growing realization of just how awesome value semantics are, dependency injection using templated, and concept conforming values, are becoming much more prevalent. I would go as far as to say it's becoming the new normal, unless you really need runtime polymorphism. It's a shame that there is no easy way to test this style of code using gtest/gmock.
I realize that this is a design issue. Providing mocking of copyable objects requires clarity on the mocking semantics. Perhaps you could provide a way of opting in to this by providing some kind of configuration of the mock object when it's defined?
This suggestion was also added to https://github.com/google/googletest/issues/3734 after it had been closed. But since there was no further activity there I assumed that the closed ticket was the wrong place for continuing this discussion.
Describe the proposal
This would be my suggested semantics:
Make the mock movable. Since there is only ever one valid mock object, that's the one that should record actions and do validation. The moved from husks should not record any further actions and not do any validation.
Make the mock copyable and treat all copies like shared_ptr to one and the same mock. I.e. all objects record actions but only the last one standing does validation. There will be some overhead to guarantee thread safety, but if this behavior is opt-in I'm sure most users would be fine with it. https://github.com/google/googletest/issues/3734 Here is an example of code that would be awesome if it was possible to get working (without having to resort to pointers and dynamic memory allocations). Here is the same thing on godbolt: https://godbolt.org/z/G83Eqcvv9
#include <concepts>
#include <memory>
template <typename T>
concept Engine = requires(T e) {
{ e.start() } -> std::convertible_to<bool>;
};
template <Engine TEngine>
struct Car {
explicit Car(const TEngine& engine) : engine(engine) {}
explicit Car(TEngine&& engine) : engine(std::move(engine)) {}
bool start() { return engine.start(); }
private:
TEngine engine;
};
struct ElectricEngine {
bool start() { return true; }
};
static_assert(Engine<ElectricEngine>);
// Test code
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::Return;
struct MockEngine {
public:
MOCK_METHOD(bool, start, (), ());
};
static_assert(Engine<MockEngine>);
TEST(car_tests, engine_start) {
// This is similar to the production code
Car electric_car{ElectricEngine{}};
ASSERT_TRUE(electric_car.start());
// This would be nice to be able to do but doesn't work without this feature
MockEngine mock_engine{};
EXPECT_CALL(mock_engine, start()).Times(1).WillOnce(Return(true));
//Car car{std::move(mock_engine)};
//or Car car{mock_engine};
//ASSERT_TRUE(car.start());
}
Is the feature specific to an operating system, compiler, or build system version?
No
Thanks! We discussed this and generally like the idea. Due to the nature of the change this is a thing we'd like to design and take through some internal API review processes. It is currently low priority, so I cannot promise we get to it any time soon, but it is on our radar.
To my mind, the most important thing is move semantics. With the prevalence of interfaces that vend objects using std::unique_ptr, the inability to return a mock object in a std::unique_ptr is a serious limitation. Copy would be nice to have, but not so important.
As someone who has been using gtest+gmock in many projects, this (https://godbolt.org/z/7x5jn4oeb) was a huge surprise.
Any C++11+ library should pretty much have everything at least moveable. I have some tests where multiple mocks have very similar or identical usage patters so I wanted to make a factory function that creates strict mocks ... turns out it is impossible because mock classes explicitly delete copy operations, which also implicitly deletes move operations.
I don't care about copy (copying objects with polymorphic behavior is generally a complex design and implementation topic) but I predict vast majority of C++11 (and above) users expect everything (except unique stuff like atomics) to be at least moveable. Being unable to do some means that various kinds of unique_ptr
and similar interfaces can not be used.