Mocking free function

38,845

Solution 1

No it's not possible, without changing the sources, or bringing your own version of foo() that is linked with the executable code.


From GoogleMock's FAQ it says

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.

Also from the Cookbook

Mocking Free Functions

It's possible to use Google Mock to mock a free function (i.e. a C-style function or a static method). You just need to rewrite your code to use an interface (abstract class).

Instead of calling a free function (say, OpenFile) directly, introduce an interface for it and have a concrete subclass that calls the free function:

class FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) = 0;
};
class File : public FileInterface {
 public:
  ...
  virtual bool Open(const char* path, const char* mode) {
    return OpenFile(path, mode);
  }
};

Your code should talk to FileInterface to open a file. Now it's easy to mock out the function.

This may seem much hassle, but in practice you often have multiple related functions that you can put in the same interface, so the per-function syntactic overhead will be much lower.

If you are concerned about the performance overhead incurred by virtual functions, and profiling confirms your concern, you can combine this with the recipe for mocking non-virtual methods.


As you mentioned in your comment that you actually provide your own version of foo(), you can easily solve this having a global instance of another mock class:

struct IFoo {
    virtual A* foo() = 0;
    virtual ~IFoo() {}
};

struct FooMock : public IFoo {
     FooMock() {}
     virtual ~FooMock() {}
     MOCK_METHOD0(foo, A*());
};

FooMock fooMock;

// Your foo() implementation
A* foo() {
    return fooMock.foo();
}

TEST(...) {
    EXPECT_CALL(fooMock,foo())
        .Times(1)
        .WillOnceReturn(new MockA());
    // ...
}

Don't forget to clear all call expectations, after each test case run.

Solution 2

There are 2 options:

If you insist on using gmock, there's an "extension" for global mocking from apriorit: https://github.com/apriorit/gmock-global

It's rather limited, though - or at least I couldn't figure out in 5 minutes how to have side effects on a mocked call.

If you're willing to switch from gmock, then hippomocks has a very neat way of doing what you want.

Here's an example for mocking fopen, fclose and fgets for testing a member function which reads from a file using cstdio (streams are very inefficient):

TEST_CASE("Multi entry") {
    std::vector<std::string> files{"Hello.mp3", "World.mp3"};
    size_t entry_idx = 0;
    MockRepository mocks;
    mocks.OnCallFunc(fopen).Return(reinterpret_cast<FILE *>(1));
    mocks.OnCallFunc(fgets).Do(
        [&](char * buf, int n, FILE * f)->char *{ 
            if (entry_idx < files.size())
            {
                strcpy(buf, files[entry_idx++].c_str());
                return buf;
            }
            else
                return 0;
            }
        );
    mocks.OnCallFunc(fclose).Return(0);

    FileExplorer file_explorer;
    for (const auto &entry: files)
        REQUIRE_THAT(file_explorer.next_file_name(), Equals(entry.c_str()));
    REQUIRE_THAT(file_explorer.next_file_name(), Equals(""));
}

Where the function under test looks like this:

string FileExplorer::next_file_name() {
    char entry[255];
    if (fgets((char *)entry, 255, _sorted_entries_in_dir) == NULL)
        return string();
    _current_idx++;
    if (_current_idx == _line_offsets.size())
        _line_offsets.push_back(static_cast<unsigned>(char_traits<char>::length(entry)) + _line_offsets.back());
    return string(entry);
} 

I'm using catch2 as the testing framework here, but I think hippomocks would work with Google's Testing framework as well (I recommend catch2, by the way, really easy to work with).

Solution 3

Of course, the answer explaining the solution according to GTest/GMock's documentation couldn't be much more correct.

But I would like to add a temporary quick&dirty approach. It should be applicable to cases where you want to get legacy C/C++ code under test as quickly and as non-invasively as possible. (Just to proceed with fixes, refactoring and more proper testing as soon as possible after.)

So, to mock a free function void foo(int) appearing in some code to be tested, within the source file you just make the following adaptions:

#if TESTING
#define foo(param) // to nothing, so calls to that disappear
#endif

// ... code that calls foo stays untouched and could be tested

The macro TESTING, indicating that the code runs under test, doesn't come with GTest/GMock - you need to add it to test targets by yourself.

The possibilities are rather limited, but you might also be able to construct something useful for return types as A* in the question's example.

Unfortunately, also this isn't a solution without changing the code. If that is really necessary, you could Google for 'link seams'. But my guess is that this could be quite a hassle in practice. And it even might not be possible at all in many/most cases?!

Solution 4

If your free function is in the form of an std::function object, you can mock it using MockFunction. See this answer

Share:
38,845
Muhammad Hassan
Author by

Muhammad Hassan

Updated on July 09, 2022

Comments

  • Muhammad Hassan
    Muhammad Hassan almost 2 years

    I am stuck in a problem and can't seem to find the solution.

    I am using VS2005 SP1 for compiling the code.

    I have a global function:

    A* foo();
    

    I have a mock class

    class MockA : public A {
    public:
        MOCK_METHOD0 (bar, bool());
        ...
    };
    

    In the sources, it is accessed like this: foo()->bar(). I cannot find a way to mock this behavior. And I cannot change the sources, so the solution in google mock cook book is out of question.

    Any help or pointers in the right direction will be highly appreciated. :)

  • leemes
    leemes about 9 years
    Quoting from the question: "the solution in google mock cook book is out of question"
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @leemes Then the answer is No.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    I have my own version of foo(), and it returns a pointer to A. But when I try to set Expectations on it, i get the error that bar() is not a member. Like, MockA mocked_A; A foo() { mocked_A = new A(); return mocked_A; } And i set the expectations like this: EXPECT_CALL (mocked_A, bar()); but i get the error i mentioned earlier. Is there anything I can do to make it work ?
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    Actually, I was just given the header file, and the lib which I link to. So, I do not link to the lib while unit testing and use my own foo() implementation.
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan Well, if you have your own version of foo(), then you could create another mock class with a non static function and the same signature, create a global instance of that one and call it from foo(). Such you can set call expectations for the global instance of that mock.
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan I've added a short sample, what it should look like.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @ πάντα ῥεῖ : Thanks a lot. It worked :) , I edited just one thing in your code, the return keyword.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @πάντα ῥεῖ: I got stuck into another problem using the above solution; Memory leaks. When my tests exit, it gives the error that the object should be deleted but is not. I changed the global obj to pointer, and deleted it at the end of test. Still getting errors. Any pointers ?
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan Probably just having a .WillOnceReturn(new MockA()); was too simplified. Do the new MockA() outside, and delete it before the testcase ends.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @ πάντα ῥεῖ: I didn't do it like that, I created a NiceMock <MockA> mockObj; for MockA, and then returned that mock obj like .WillOnce(Return(&mockObj)). I will try your method too. Thanks.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @πάντα ῥεῖ I still get memory leaks with the method. And I read somewhere that we cannot have global variables in gmock. So, how do I counter this problem?
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan Sorry, I didn't realize it was you, when I duped your last question. Please ask it again, may be someone else can help you with this problem.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @πάντα ῥεῖ haha. no problem :) I will ask again, i just deleted it, will undelete it but if you find a way to fix memory leaks, please let me know. :) Also, can you unmark it as duplicate ? Thanks.
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan You should be able to see your deleted question. Just go there, click edit and copy all the text for a new question.
  • Muhammad Hassan
    Muhammad Hassan about 9 years
    @πάντα ῥεῖ Yep, I undeleted it. Thanks. So, is it possible to unmark it as duplicate ?
  • πάντα ῥεῖ
    πάντα ῥεῖ about 9 years
    @MuhammadHassan Yes I'll do.
  • Bob
    Bob almost 7 years
    @πάνταῥεῖ someone should add an answer with that solution.
  • πάντα ῥεῖ
    πάντα ῥεῖ almost 7 years
    @Adrian What do you mean? What's actually unclear here? Feel free to provide another answer with that solution.