23

I'm maintaining a legacy project written in C and it's unfeasible to get it running with a C++ compiler. Since the code is cross compiled it is however possible to run unit-tests or similar in a host environment. hence it's also possible to interface with a C++ host compiler and use google-test and google-mock.

There are certain capabilities of google-mock which seem to be very tempting to be used for testing as invoking real implementations and setting call expectations.

I would like to be able to use them in C code. I can see that it is indeed possible to use google-mock without using vtables, but it requires templates.

Is there a way to mock bare C functions with google mock?

EDIT:

I basically had to use google mock, I assume though that everybody else who will read this thread has better flexibility than me.

Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
  • I'm curious what in a C project won't compile with a C++ compiler. – Carey Gregory Oct 23 '13 at 21:41
  • 2
    @carygregory sometimes it's simple things as `foobar * x = malloc(sizeof(foobar));` instead of casting it to `(foobar *)malloc(sizeof(foobar));` – Alexander Oh Oct 23 '13 at 21:46
  • 2
    You normally need to add a ton of casts when you move C code to C++ (e.g. for all calls to malloc and anywhere else that you can no longer implicitly convert a void * to a typed pointer). – Paul R Oct 23 '13 at 21:46
  • I think the ++ operators when used on an enum in C advances the variable to the next value of the enum. In C++ it just adds one. I'm sure there are other differences. – HeywoodFloyd Oct 23 '13 at 21:47
  • 2
    @HeywoodFloyd People who use `++` on an `enum` probably deserve whatever they get. ;-) – Carey Gregory Oct 23 '13 at 21:53
  • 1
    @CareyGregory and besides that, who said that the target has a C++ compiler. we have 8051 and some other embedded architectures. – Alexander Oh Oct 24 '13 at 11:02

4 Answers4

16

I found a way to be able to mock bare C functions in google-mock.

The solution is to declare foobar to be a weak alias that maps to foobarImpl. In production code you do not implement foobar() and for unit tests you provide an implementation that calls a static mock object.

This solution is GCC specific but there are other compilers/linkers that provide weak aliasing.

  • rename the function void foobar(); to void foobarImpl();
  • add an attribute to the function foobar like: void foobar() __attribute__((weak, alias("foobarImpl") ));
  • if you want to have a non weak alias use a preproessor directive to remove the weak from the attributes.

Hence:

#pragma once
void foobar();

becomes

// header.h
#pragma once

void foobar();    
void foobarImpl(); // real implementation

and

extern "C" {
#include "header.h"
}
// code.c
void foobarImpl() {
  /* do sth */
}
void foobar() __attribute__(( weak, alias ("foobarImpl") )); // declare foobar to be a weak alias of foobarImpl

This will tell the gnu linker to link calls of foobar() with foobarImpl() whenever there is no symbol called foobar()

then add the testing code

struct FooInterface {
   virtual ~FooInterface() {}
   virtual void invokeFoo() const { }
};

class MockFoo : public FooInterface {
public:
  MOCK_CONST_METHOD0(invokeFoo, void());
}

struct RealFoo : public FooInterface {
   virtual ~RealFoo() {}
   virtual void invokeFoo() const { foobarImpl(); }
};

MockFoo mockFoo;
RealFoo realFoo;
void foobar() {
  mockFoo.invokeFoo();
}

if this code is compiled and linked it will replace foobar with the mock call. if you really want to call foobar() you can still do add a default invocation.

ON_CALL(mockFoo, invokeFoo())
       .WillByDefault(Invoke(&realFoo,&RealFoo::invokeFoo));
Alexander Oh
  • 24,223
  • 14
  • 73
  • 76
  • 2
    Nice to see that you found a feasible way to wrap the C API functions. That kind of solution/work around was, what I've had in mind ... – πάντα ῥεῖ Nov 03 '13 at 21:52
  • I have some difficulties understanding the answer. Perhaps it would help if you 1. explain, what host and target mean (original/renamed C function, C++ replacement?), 2. correct the use of the undeclared `FooImpl`, 3. say, why there is no `extern "C"` mentioned anywhere, 4. explain, where the mentioned strong alias is used - I can only spot a declaration of a weak one with the option to make it strong (non weak)? – yau Jan 13 '18 at 14:22
  • @yau cool thanks for the comment. I'll redact the answer, but if you look at the question this is about cross compiling C code, hence the wording for target/host, which is standard names for that scenario. I can redact the answer to more generally be applicable. 3.) is required by the standard but this is a GCC specific solution and gcc won't complain aber the missing mangling declaration. 4. there is no strong alias declaration, a strong alias is just the default behavior of the linker when seeing duplicate symbols. I'll redact to not have the wording strong alias in the A. – Alexander Oh Jan 13 '18 at 20:36
  • here is an answer for target/host on gcc: https://stackoverflow.com/q/7088576/887836. I did not put `extern "C"` anywhere, because the code is still compiled with a real C compiler and I don't want to clutter C code with C++ constructs and use the preprocessor to remove them again. – Alexander Oh Jan 13 '18 at 20:50
3

As from the Google Mock FAQ:

My code calls a static/global function. Can I mock it?
You can, but you need to make some changes.

In general, if you find yourself needing to mock a static function, it's a sign that your modules are too tightly coupled (and less flexible, less reusable, less testable, etc). You are probably better off defining a small interface and call the function through that interface, which then can be easily mocked. It's a bit of work initially, but usually pays for itself quickly.

This Google Testing Blog post says it excellently. Check it out.

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
2

Your question specifically mentions Google Mock, but then does not state any other reason for using that framework. The other answer suggests using a workaround which seems unnecessarily intrusive.

Hence I hope I am allowed to make an alternative suggestion which works well without having to use weak aliases etc.

I have used CppUTest (https://cpputest.github.io/) for unit test with mock, successfully on a couple of large mainly-C projects (some C++). The mocking works without having to resort to any subterfuge of the above sort.

Unfortunately the project documentation is a little weak, some better (if a little agile-doctrinaire) information and examples in the book (also seen circulating as a PDF) "Test Driven Development for Embedded C" - James W Greening (ISBN-13: 978-1-934356-62-3)

MikeW
  • 5,504
  • 1
  • 34
  • 29
  • Well there was reasons why we used google mock and using another framework was not an option. Suggesting alternatives is always welcome, but not everyone has the possibility to use the alternative (imagine there are 200000 tests with C++ gmock already, would you port all of them to cpputest?). When posting alternatives, please give a minimal working example to show the benefits. – Alexander Oh Nov 21 '16 at 11:53
  • I fully appreciate that, which is why I was so tentative about giving my answer ! Since the question did not ask for competing examples, I did not go so far as to unearth my 2014 code and create some examples, sorry - I thought just providing a pointer to an alternative would suffice in a case where no actual alternative was asked for ! I am very wary of giving ill-founded "answers", in which the Questioner asks about "framework X" and the Answerer says "have you tried Framework Y ?" In this case I thought it at least worth mentioning given the slight advantage, without going in too deep. – MikeW Nov 21 '16 at 11:58
  • 1
    If the answer helps anybody who found the question, I'm happily upvoting. Not everybody is looking for the same answer that I was looking for in the past. I'm not allowed to make changes to that piece of code anymore. – Alexander Oh Nov 21 '16 at 12:00
  • 1
    I may well add an example when time permits. – MikeW Nov 21 '16 at 12:04
2

I realize this is a super old thread, but I'm hoping I can make someone's life a little easier if and when they come across this question.

You can very easily autogenerate mocks for C functions that are compatible with GoogleTest using Mimicc. Find whatever header files declare the functions you want to mock, "compile" them into mock implementation object files, and link them into your test binary including definitions of the mock_fatal() and mock_failure() functions as described in the User Guide specifically for Google Test. You'll have to use the Mimicc API for interacting with the Mimicc-generated mocks (i.e. it doesn't use GoogleMock's API for setting expectations, etc.), but they can comfortably live alongside the GoogleMock-generated mocks.

To be more concrete, suppose you have a C header file foo.h which declares a few functions you want to mock. For example:

/*!
 * @param[out] pBuf Destination buffer to read to
 * @param[in] sz Size of the read buffer
 */
int magic_read(char *pBuf, const size_t sz);

/*!
 * @param[in] pBuf Source buffer to write from
 * @param[in] sz Size of the write buffer
 */
int magic_write(const char *pBuf, const size_t sz);

You can create mocks for these by compiling foo.h with all the same CFLAGS that would be used to compile the accompanying production foo.c :

prompt$ mimicc -c foo.h -o mock.o --hout=foo-mock.h -DSOME_PREPROC=1 -I <your includes>

To use this in a test, set expectations and returns using the API declared in foo-mock.h as shown on the command line invocation above. Include the implementation of mock_fatal() and mock_failure() for Google Test.

#include <gtest/gtest.h>
#include <memory>

std::unique_ptr<char []> mockErrStr(const char *pLocation, unsigned count, const char *pMsg)
{
    const char *pFmtStr = "mock assertion failure! location: '%s',"
                          " iteration: %d, message: %s";
    size_t n = snprintf(NULL, 0, pFmtStr, pLocation, count, pMsg);
    std::unique_ptr<char []> outStrBuf(new char[n+1]);
    snprintf(outStrBuf.get(), n+1, pFmtStr, pLocation, count, pMsg);
    return outStrBuf;
}

void mock_failure(const char *pLocation, unsigned count, const char *pMsg)
{
    ADD_FAILURE() << mockErrStr(pLocation, count, pMsg).get();
}

void mock_fatal(const char *pLocation, unsigned count, const char *pMsg)
{
    FAIL() << mockErrStr(pLocation, count, pMsg).get();
    exit(1);
}

TEST_F(MimiccPoC, test1)
{
    char mock_ret_data[] = "HELLO WORLD";
    MOCK_FUNCTIONS(foo).magic_read.expect(32);
    MOCK_FUNCTIONS(foo).magic_read.andReturn(
            1, mock_ret_data, sizeof(mock_ret_data));

    char testRetBuf[32];
    int testRes = magic_read(testRetBuf, sizeof(testRetBuf));
    ASSERT_EQ(1, testRes);
    ASSERT_STREQ(testRetBuf, "HELLO WORLD");
}

While this may seem like a lot, once the plumbing is set up, you can automatically mock any C or C++ code you have without actually having to write or maintain additional mock code, you just focus on the tests. Quite a bit easier in the long run.

Jon Reeves
  • 2,426
  • 3
  • 14
  • honestly whatever helps someone and is more modern than my solution that is half a decade old should be considered a valuable contribution. let me read and check and give you the upvote once I spent the time. And thanks – Alexander Oh May 13 '21 at 19:19