24

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. :)

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Muhammad Hassan
  • 501
  • 1
  • 4
  • 14

5 Answers5

22

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.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 1
    Quoting from the question: "the solution in google mock cook book is out of question" – leemes Feb 08 '15 at 09:15
  • 4
    @leemes Then the answer is No. – πάντα ῥεῖ Feb 08 '15 at 09:18
  • 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 Feb 08 '15 at 10:22
  • 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. – Muhammad Hassan Feb 08 '15 at 10:27
  • 1
    @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. – πάντα ῥεῖ Feb 08 '15 at 10:29
  • @MuhammadHassan I've added a short sample, what it should look like. – πάντα ῥεῖ Feb 08 '15 at 11:29
  • @ πάντα ῥεῖ : Thanks a lot. It worked :) , I edited just one thing in your code, the return keyword. – Muhammad Hassan Feb 09 '15 at 07:26
  • @πάντα ῥεῖ: 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 ? – Muhammad Hassan Feb 09 '15 at 13:48
  • @MuhammadHassan Probably just having a `.WillOnceReturn(new MockA());` was too simplified. Do the `new MockA()` outside, and `delete` it before the testcase ends. – πάντα ῥεῖ Feb 09 '15 at 16:01
  • @ πάντα ῥεῖ: I didn't do it like that, I created a NiceMock mockObj; for MockA, and then returned that mock obj like .WillOnce(Return(&mockObj)). I will try your method too. Thanks. – Muhammad Hassan Feb 10 '15 at 08:12
  • @πάντα ῥεῖ 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? – Muhammad Hassan Feb 23 '15 at 08:10
  • @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. – πάντα ῥεῖ Feb 23 '15 at 08:15
  • @πάντα ῥεῖ 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. – Muhammad Hassan Feb 23 '15 at 08:18
  • @MuhammadHassan You should be able to see your deleted question. Just go there, click edit and copy all the text for a new question. – πάντα ῥεῖ Feb 23 '15 at 08:20
  • @πάντα ῥεῖ Yep, I undeleted it. Thanks. So, is it possible to unmark it as duplicate ? – Muhammad Hassan Feb 23 '15 at 08:22
  • @MuhammadHassan Yes I'll do. – πάντα ῥεῖ Feb 23 '15 at 08:43
  • @πάνταῥεῖ someone should add an answer with that solution. – Bob May 16 '17 at 21:11
  • @Adrian What do you mean? What's actually unclear here? Feel free to provide another answer _with that solution_. – πάντα ῥεῖ May 16 '17 at 21:13
4

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).

Mr.WorshipMe
  • 713
  • 4
  • 16
2

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?!

yau
  • 537
  • 6
  • 14
1

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

Rufus
  • 5,111
  • 4
  • 28
  • 45
0

What has worked for me is

  • to define A* foo() in a separate source file foo.cpp in the main project,
  • not to include foo.cpp in the test project,
  • include a different source file mock-foo.cpp in the test project that provides the mock implementation of A* foo().

For example, pseudocode for the main project file (e.g. .vcxproj or CMakeLists.txt):

include src/foo.hpp # declare A* foo()
include src/foo.cpp # define A* foo()

and the test project file:

include src/foo.hpp
include test/mock-foo.cpp # define mocked A* foo()

Simple and sweet, but may or may not work in your case.

mrts
  • 16,697
  • 8
  • 89
  • 72