Open paulreimer opened 6 years ago
With a bit of trickery, you can, for virtual functions.
class C
{
public:
virtual int foo(int x, int y) { ... }
};
using trompeloeil::_;
class SpyC : public C
{
public:
MAKE_MOCK2(spy_foo, void(int,int));
int foo(int x, int y) { spy_foo(x,y); return C::foo(x,y); }
};
Then you can spy on it from your test program:
SpyC m;
REQUIRE_CALL(m, spy_foo(_,_));
...
It's not very pretty, though. I'll see if I can come up with something better, but for now this is the best I can offer.
An alternative technique:
class MockC : public C
{
public:
MAKE_MOCK2(foo, int(int,int));
};
and use with explicitly pointing out the base in a RETURN from the test
MockC m;
REQUIRE_CALL(m, foo(_,_))
.LR_RETURN(m.C::foo(_1,_2));
You need to use the LR_
version to get a non-const reference to the object.
Great! I'll probably use the second/alternative, seems like there is more flexibility when setting up the expectations.
The second approach seems to be a bit closer to how it would work in GMock also. The language in GMock seems a bit more clear (WillOnce(Invoke(&m, ...))
, but I can see what's going on pretty easily in both your examples, so thanks!
I'll continue the discussion here from #22 to keep things centralized.
One approach is to specialize your macros to handle automatic invocation of base class methods. Example:
MockC m;
REQUIRE_CALL(m, foo(_,_))
.RETURN_BASE(_1, _2);
Similarly, you'd have a SIDE_EFFECT_BASE
as well. The purpose of the _BASE
versions of each of these is to simply wrap the call to m.C::foo
to eliminate boilerplate and make things less error prone. It also allows you to perform tasks in addition to invoking the base override, as you'd normally do an a derived overridden function:
MockC m;
REQUIRE_CALL(m, foo(_,_))
.SIDE_EFFECT(std::cout << "Do this step before invoking base function\n")
.RETURN_BASE(_1, _2);
I think all this needs is a little bit of sugarcoating, TBH.
Unfortunately that won't work, for two reasons.
REQUIRE_CALL()
expands to a complete function call and the return value from that is what .RETURN
(or in your case .RETURN_BASE
) calls member functions on. The value returned holds the type of the function called, so that it can do proper signature checks, but which exact function it is, is already forgotten. .RETURN_BASE()
cannot see what function it is. If it was allowed for the expansion of a macro to define another macro, it could be done, but it's not allowed. It could store the function as a pointer-to-member, but this doesn't work becase:
It is not possible to programmatically figure out the base classes (if any) of a class. So MockC
has no way of knowing that it should look for a C
. This is complicated further by the possibility that MockC
may have multiple bases.
This will of course change dramatically if we ever get metaclasses to C++, but that'll be a completely different mocking framework with far fewer macros.
However, I get the impression from your question/suggestion, that perhaps you'd want more than one MockC
, and you pick and choose between test cases which ones you use.
struct MockC : C
{
MAKE_MOCK2(foo, int(int,int), override);
MAKE_MOCK1(bar, int(int), override);
};
struct MockC_log_foo : C
{
int foo(int x, int y) override { std::cout << "C::foo(" << x << ", " << y << ")\n"; return C::foo(x,y); }
MAKE_MOCK1(bar, int(int), override);
};
Then, in tests where you're really interested in calls to foo
, use MockC
. In tests where foo
is not really interesting at all and you just want the default behaviour, use MockC_log_foo
.
You could make it a requirement (via documentation) that if you plan to use any of the _BASE
macros, you must have a corresponding alias defined that identifies the base class. This alias would need to be a member of the mock subclass. Example:
class MockFoo : public Foo
{
public:
using Super = Foo;
MAKE_MOCK1(foo, int(int, int));
};
You could maybe take it a step further by making another macro to automatically define this alias somehow, maybe a MAKE_MOCK_SUBCLASS
macro, instead of letting the user define the class themselves:
MAKE_MOCK_SUBCLASS_BEGIN(MockFoo, Foo)
MAKE_MOCK1(foo, int(int, int));
MAKE_MOCK_SUBCLASS_END();
I know this gives you creepy MFC-vibes, but it's just an example. How you sugar-coat this is up for debate, but I can think of several ways you could extend Trompeloeil to automate things a bit better by controlling the class definition more strongly. I'm not sure if you have functional reasons for not doing this, though.
Wondering how this is possible, ideally for non-virtual functions as well.
I know it is a questionable practice, but GMock supports it and it has been invaluable for testing into legacy code or "untestable" collaborators.
https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#delegating-calls-to-a-parent-class