eranpeer / FakeIt

C++ mocking made easy. A simple yet very expressive, headers only library for c++ mocking.
MIT License
1.24k stars 173 forks source link

Proposed Feature with working example - Free Function mocking #163

Open belchercw opened 5 years ago

belchercw commented 5 years ago

Mocking free functions is useful when calling into C APIs or simply non-member C++ functions. I've hacked together an example that might be useful for others, or for inclusion in the library. Probably needs some T's crossed and I's dotted to work for all use-cases. Here's an example of the result:


//Normally these would come from the external component's header
extern "C"
{
    void Function1(void);
}
float Function2(int);

//declare the mocks (must match the signature from component's API)
MockFree(void, Function1)
MockFree(float, Function2, int)

//use the mocks
TEST_CASE("Mocking")
{
    //free function mock
    fakeit::When(FreeFunction(Function1)).Do([]() {std::cout << "Called Function1()" << std::endl; });
    fakeit::When(FreeFunction(Function2)).Return(10.0f);
    Function1();

    REQUIRE(Function2(5) == 10.0f);
}

Someone with better macro foo than I could probably do a better job, but I've copied the implementation I hacked together below. The macros for creating the argument lists are the bulk of it (which I shamelessly stole off stack overflow). Maybe there's a better way? Scroll to the namespace fakeit at the bottom for the interesting part.

BTW - Great job on FakeIt! Amazing library - would love a tutorial on the crazy VTable hackery going on.

#pragma once
#include "fakeit.hpp"

#ifdef _MSC_VER // Microsoft compilers

#define GET_ARG_COUNT(...)  INTERNAL_EXPAND_ARGS_PRIVATE(INTERNAL_ARGS_AUGMENTER(__VA_ARGS__))

#define INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__
#define INTERNAL_EXPAND(x) x
#define INTERNAL_EXPAND_ARGS_PRIVATE(...) INTERNAL_EXPAND(INTERNAL_GET_ARG_COUNT_PRIVATE(__VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
#define INTERNAL_GET_ARG_COUNT_PRIVATE(_1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#else // Non-Microsoft compilers

#define GET_ARG_COUNT(...) INTERNAL_GET_ARG_COUNT_PRIVATE(0, ## __VA_ARGS__, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define INTERNAL_GET_ARG_COUNT_PRIVATE(_0, _1_, _2_, _3_, _4_, _5_, _6_, _7_, _8_, _9_, _10_, _11_, _12_, _13_, _14_, _15_, _16_, _17_, _18_, _19_, _20_, _21_, _22_, _23_, _24_, _25_, _26_, _27_, _28_, _29_, _30_, _31_, _32_, _33_, _34_, _35_, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, _65, _66, _67, _68, _69, _70, count, ...) count

#endif

#define MAKE_PARAMS_0()
#define MAKE_PARAMS_1(type) type arg1
#define MAKE_PARAMS_2(type1, type2) type1 arg1, type2 arg2
#define MAKE_PARAMS_3(type1, type2, type3) type1 arg1, type2 arg2, type3 arg3
//.. add as many MAKE_PARAMS_* as you need

#define MAKE_PARAMS_N(N, ...) MAKE_PARAMS_##N(__VA_ARGS__)
#define MAKE_PARAMS_FORCE_N(N, ...) MAKE_PARAMS_N(N, __VA_ARGS__)
#define MAKE_PARAMS(...) MAKE_PARAMS_FORCE_N(GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)

#define MAKE_ARGS_0()
#define MAKE_ARGS_1(type) arg1
#define MAKE_ARGS_2(t1, t2) arg1, arg2
#define MAKE_ARGS_3(t1, t2, t3) arg1, arg2, arg3
//.. add as many MAKE_ARGS_* as you have MAKE_PARAMS_*

#define MAKE_ARGS_N(N, ...) MAKE_ARGS_##N(__VA_ARGS__)
#define MAKE_ARGS_FORCE_N(N, ...) MAKE_ARGS_N(N, __VA_ARGS__)
#define MAKE_ARGS(...) MAKE_ARGS_FORCE_N(GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)

namespace fakeit
{

 //free function mock helper for fakeit
#define FreeFunction(Function) Method(Function ## _mock, Function)
#define MockFree(ReturnType, Function, ...) \
struct Function ## _interface \
{ \
   virtual ReturnType Function(MAKE_PARAMS(__VA_ARGS__)) = 0; \
}; \
static fakeit::Mock<Function ## _interface> Function ## _mock; \
ReturnType Function(MAKE_PARAMS(__VA_ARGS__)) \
{ \
    return Function ## _mock.get().Function(MAKE_ARGS(__VA_ARGS__)); \
};
}
thomasantony commented 5 years ago

@belchercw I tried running your example using gcc 8.2.0 . I keep getting this error:

In file included from /src/tests/test_monitor.cpp:2:
/src/thirdparty/fakeit_freefn.hpp:22:34: error: 'arg1' has not been declared
 #define MAKE_PARAMS_1(type) type arg1
                                  ^~~~
/src/thirdparty/fakeit_freefn.hpp:27:31: note: in expansion of macro 'MAKE_PARAMS_1'
 #define MAKE_PARAMS_N(N, ...) MAKE_PARAMS_##N(__VA_ARGS__)
                               ^~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:28:37: note: in expansion of macro 'MAKE_PARAMS_N'
 #define MAKE_PARAMS_FORCE_N(N, ...) MAKE_PARAMS_N(N, __VA_ARGS__)
                                     ^~~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:29:26: note: in expansion of macro 'MAKE_PARAMS_FORCE_N'
 #define MAKE_PARAMS(...) MAKE_PARAMS_FORCE_N(GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
                          ^~~~~~~~~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:50:32: note: in expansion of macro 'MAKE_PARAMS'
    virtual ReturnType Function(MAKE_PARAMS(__VA_ARGS__)) = 0; \
                                ^~~~~~~~~~~
/src/tests/test_monitor.cpp:15:1: note: in expansion of macro 'MockFree'
 MockFree(void, Function1)
 ^~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:53:45: error: variable or field 'Function1' declared void
 ReturnType Function(MAKE_PARAMS(__VA_ARGS__)) \
                                             ^
/src/tests/test_monitor.cpp:15:1: note: in expansion of macro 'MockFree'
 MockFree(void, Function1)
 ^~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:22:34: error: 'arg1' was not declared in this scope
 #define MAKE_PARAMS_1(type) type arg1
                                  ^~~~
/src/thirdparty/fakeit_freefn.hpp:27:31: note: in expansion of macro 'MAKE_PARAMS_1'
 #define MAKE_PARAMS_N(N, ...) MAKE_PARAMS_##N(__VA_ARGS__)
                               ^~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:28:37: note: in expansion of macro 'MAKE_PARAMS_N'
 #define MAKE_PARAMS_FORCE_N(N, ...) MAKE_PARAMS_N(N, __VA_ARGS__)
                                     ^~~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:29:26: note: in expansion of macro 'MAKE_PARAMS_FORCE_N'
 #define MAKE_PARAMS(...) MAKE_PARAMS_FORCE_N(GET_ARG_COUNT(__VA_ARGS__), __VA_ARGS__)
                          ^~~~~~~~~~~~~~~~~~~
/src/thirdparty/fakeit_freefn.hpp:53:21: note: in expansion of macro 'MAKE_PARAMS'
 ReturnType Function(MAKE_PARAMS(__VA_ARGS__)) \
                     ^~~~~~~~~~~
/src/tests/test_monitor.cpp:15:1: note: in expansion of macro 'MockFree'
 MockFree(void, Function1)
 ^~~~~~~~
/src/tests/test_monitor.cpp: In function 'void ____C_A_T_C_H____T_E_S_T____0()':
/src/tests/test_monitor.cpp:22:101: error: no matching function for call to 'fakeit::WhenFunctor::MethodProgress<void, int>::Do(____C_A_T_C_H____T_E_S_T____0()::<lambda()>)'
     fakeit::When(FreeFunction(Function1)).Do([]() {std::cout << "Called Function1()" << std::endl; });
                                                                                                     ^
In file included from /src/tests/test_monitor.cpp:1:
/src/thirdparty/fakeit.hpp:7259:59: note: candidate: 'fakeit::MethodStubbingProgress<void, arglist ...>& fakeit::MethodStubbingProgress<void, arglist ...>::Do(std::function<void(const typename fakeit::test_arg<arglist>::type ...)>) [with arglist = {int}]'
         virtual MethodStubbingProgress<void, arglist...> &Do(
                                                           ^~
/src/thirdparty/fakeit.hpp:7259:59: note:   no known conversion for argument 1 from '____C_A_T_C_H____T_E_S_T____0()::<lambda()>' to 'std::function<void(int&)>'
/src/thirdparty/fakeit.hpp:7302:9: note: candidate: 'template<class F> fakeit::MethodStubbingProgress<void, arglist ...>& fakeit::MethodStubbingProgress<void, arglist ...>::Do(const fakeit::Quantifier<R>&) [with F = F; arglist = {int}]'
         Do(const Quantifier<F> &q) {
         ^~
/src/thirdparty/fakeit.hpp:7302:9: note:   template argument deduction/substitution failed:
/src/tests/test_monitor.cpp:22:101: note:   '____C_A_T_C_H____T_E_S_T____0()::<lambda()>' is not derived from 'const fakeit::Quantifier<R>'
     fakeit::When(FreeFunction(Function1)).Do([]() {std::cout << "Called Function1()" << std::endl; });
                                                                                                     ^
In file included from /src/tests/test_monitor.cpp:1:
/src/thirdparty/fakeit.hpp:7308:9: note: candidate: 'template<class first, class second, class ... tail> fakeit::MethodStubbingProgress<void, arglist ...>& fakeit::MethodStubbingProgress<void, arglist ...>::Do(const first&, const second&, const tail& ...) [with first = first; second = second; tail = {tail ...}; arglist = {int}]'
         Do(const first &f, const second &s, const tail &... t) {
         ^~
/src/thirdparty/fakeit.hpp:7308:9: note:   template argument deduction/substitution failed:
/src/tests/test_monitor.cpp:22:101: note:   candidate expects at least 2 arguments, 1 provided
     fakeit::When(FreeFunction(Function1)).Do([]() {std::cout << "Called Function1()" << std::endl; });
                                                                                                     ^
thomasantony commented 5 years ago

Problem was that the GET_ARG_COUNT macro would return 0 even when there were no arguments. I fixed it by using this implementation instead.


#define GET_ARG_COUNT(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
#define Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args

#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0

#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n

I am not sure how well that would work in Visual Studio though. I have only tested it on GCC 8.2.0 .

MarcelRobitaille commented 5 years ago

Just mocking your float Function2(int); I am getting Function2 is not a type. I am using g++ 8.2.1.

efagerberg commented 4 years ago

I would love to see this feature included into this library. Unfortunately without it, a user is most likely forced to group functions into a class (And they cannot be static functions), in order to get the mocking behavior. I also like the nomenclature in this example.

fgr-17 commented 3 years ago

:( this would be great for embedded systems, where you have freeRTOS C functions and C++ app code

otrempe commented 1 year ago

I would love to have free function mocks, This would simplify testing higher level functions calling lower level free functions.

However, I don't see how this strategy could be useful. The calling code will still call the real function. The mock will never be invoked. Unless the calling code gets the free function injected somehow.

FranckRJ commented 1 year ago

I have never tried it but if someone's looking for a library that already does that there's powerfake : https://github.com/hedayat/powerfake

From what I understand it can interface with FakeIt, so you can use both. But as I said I never tried so I'm not sure exactly how it works.

hedayat commented 1 year ago

@FranckRJ Thanks for mentioning my tool. :)

Currently, it uses a feature of GNU ld to provide a replacement function at link time. Therefore, it has some limitations, for example it cannot catch inlined function calls. It also provides a limited way of capturing calls in the same translation unit which might not work in some optimized contexts and also you lose access to the real function completely.

Finally, it doesn't work with anything other than GNU ld or compatible linkers.