Mocking C functions in MSVC (Visual Studio)

2019-07-14 15:56发布

问题:

I am reading several articles on mocking C functions (like CMock, or CMocka), but I am not sure how the actual functions are replaced with mocked functions in this process. For example, CMocka relies on automatic wrapping using a GNU compiler, which supports parameters like --wrap to append the __wrap prefix to function calls, or weak symbols which allow you to override any symbol you like.

But how do you do this in Visual Studio, for pretty much all other frameworks?

For example, CMock has an example similar to this (simplified a lot here):

// myfunc.c
#include <parsestuff.h>

// this is the function we would like to test
int MyFunc(char* Command)
{
    // this is the call to the function we will mock
    return ParseStuff(Command);
}

There is also the actual implementation, which contains the actual function the linker should find in the actual application:

// parsestuff.c

int ParseStuff(char* cmd)
{
    // do some actual work
    return 42;
}

Now, during testing the Ruby script creates mock functions like:

// MockParseStuff.c (auto created by cmock)

int ParseStuff(char* Cmd);
void ParseStuff_ExpectAndReturn(char* Cmd, int toReturn);
  1. But if the VS project already includes parsestuff.c, how will it be possible that the call from myfunc.c ends up in MockParseStuff.c?

  2. Does this mean I cannot have parsestuff.c included in the unit testing project? But if this is the case, then it's also impossible to mock, for example, MyFunc from myfunc.c in any tests, since I already had to include the file it in order to test it?

(Update) I am also aware that I can include the .c file instead of the .h file, and then do some preprocessor stuff to replace the original call, like:

// replace ParseStuff with ParseStuff_wrap
#define ParseStuff ParseStuff_wrap
// include the source instead of the header
#include <myfunc.c>
#undef ParseStuff

int ParseStuff_wrap(char* cmd) 
{
    // this will get called from MyFunc,
    // which is now statically included
}

but this seems like a lot of plumbing, and I don't even see it mentioned anywhere.

回答1:

Here's a simple and short solution with hippomocks:

I created an empty Win32 console application with

  • main.cpp
  • myfunc.c + myfunc.h
  • parsestuff.c, parsestuff.h

and added the code from your example.

With help of hippomocks, you can mock every C-Function. Here's how my main.cpp looks like:

#include "stdafx.h"
#include "myfunc.h"
#include "hippomocks.h"


extern "C" int ParseStuff(char* cmd);

int _tmain(int argc, _TCHAR* argv[])
{
    MockRepository mocks;

    mocks.ExpectCallFunc(ParseStuff).Return(4711);

    char buf[10] = "";

    int result = MyFunc(buf);

    return result; //assert result is 4711
}

HippoMocks is a free, simple and very powerful one-header framework and can be downloaded on GitHub.

Hope I've earned the bounty :)

UPDATE, How it works:

  1. HippoMocks gets the func pointer to ParseStuff
  2. HippoMocks builds a replacement func pointer to a template function with same signature and own implementation.
  3. Hippomocks patches the jmp opcode from the function call prologue in memory, so that it points to the replaced function.
  4. Replacement and memory patch are released after call or in destructor.

Here's how it looks like on my machine:

@ILT+3080(_ParseStuff):
00D21C0D  jmp HippoMocks::mockFuncs<char,int>::static_expectation1<0,char *> (0D21DB1h)  

If you watch the memory address 00D21C0D (may differ from run to run) in memory window, you will see, that it gets patched after the call of ExpectCallFunc.



回答2:

I have not dealt with the C mocking libraries or Visual Studio, but I have thought about this in my own project. The Feathers book suggests the preprocessor seam or the link seam as a tool for dealing with this. You already mentioned the preprocessor seam, so I'll focus on the link seam.

The link seam requires the mocked function to be in a library, and the mock function to be in a library. The test can link against the mock function library while the target application can link against the original library.

Of course, as you mention, to mock MyFunc() you will have to create another library and a separate test application to link against it (or dynamically load and unload libraries in the test application).

It sounds quite laborious which is why I am procrastinating adding tests in my own application!

Hope this helps!