Best simple way to mock static/global function?
Asked Answered
J

4

9

I have a simple almost value-like class like Person:

class Person
{
public:
    Person(ThirdPartyClass *object);
    virtual ~Person(void);

    virtual std::string GetFullName() const;
    virtual int GetAge() const;
    virtual int GetNumberOfDaysTillBirthday() const;
};

I'm using a third party library and the ThirdPartyClass needs to have a global/static function called Destroy (part of the 3rd party library) called on it to destroy it. This Destroy function is called in the Person destructor.

Now I'm trying to unit test my Person class and I need a way to mock/stub the Destroy method. I think I could write a wrapper class around the static Destroy function and then use dependency injection to inject this wrapper into the Person class, but it seems like overkill to do that just to call this one function on this simple class. What's a simple straightforward way to do this? Or is dependency injection really the best way to do this?

Update

Ultimately I decided to go with creating a class that wrapped all the 3rd party library's global functions and then using dependency injection to pass this class into the constructor of my person class. This way I could stub out the Destroy method. Although the person class only uses a single function, the other functions of the library are called at other points in my code and as I needed to test those I would face the same issue.

I create a single instance of this wrapper class in my main app code and inject it where needed. I chose to go this route because I think it's clearer. I like Billy ONeal's solution and I think it answers my question, but I realized if I were to leave the code for a few months and come back it would take me longer to figure out what was happening as compared to dependency injection. I'm reminded of the zen of python aphorism "Explicit is better than implicit." and I feel dependency injection makes what's happening a bit more explicit.

Justin answered 30/6, 2011 at 23:33 Comment(6)
What's wrong with just creating a static/global function as a stub and calling it?Gatepost
@littleadv: Well, I'm just getting into unit testing, but my understanding is you don't want to modify the class that you are testing just to test it. So if I understand you correctly, by creating a stub Destroy method and using that in my Person class I'm changing my person class and then I'd have to somehow switch between the test version and the production version.Justin
@Justin - don't modify the class you're testing, implement your own ThirdPartyClass instead as a stub.Gatepost
@littleadv: I have stubbed out ThirdPartyClass using googlemock, but ThirdPartyClass needs to have Destroy called on it to clean up.Justin
@Justin - then you found a bug in your code - Person doesn't call Destroy. I though you asked how to implement it, how to call it is entirely different issue.Gatepost
@littleadv: I don't follow you. Destroy is a global/static function and it is called in the destructor for the Person class (as I wrote it).Justin
F
10

Create a link seam. Put your destroy declaration in a header, and then have two implementation files:

// Destroy.cpp
void Destroy()
{
    //Code that really does destruction
}

And for testing:

// DestroyFake.cpp
void Destroy()
{
    //Code that does fake destruction
}

Then link the first file to your main binary, and the second file to your testing binary.

Beyond this, what you ask for is impossible. The linker bakes calls to global functions and static methods into direct jumps into the callee code -- there's no lookup or decision process to hook into to create any type of overload like the one you're looking for (beyond having Destroy calling something that's more easily mocked).

Fantasist answered 30/6, 2011 at 23:58 Comment(4)
Do you mean create two separate static lib projects, for the two versions of Destroy? That is all I know how to link.. or did you have a different idea in mind for linking the files?Justin
@User: Put the common code in the static lib. Put the first one in your main EXE project, and the second in your test EXE project. No need to split these two into static libs.Fantasist
And then where does the Destroy header file go?Justin
@User: Well, the header files are not compiled, so it really does't matter. I'd put the header in the static lib "project" myself but MSVC++ does not care.Fantasist
R
5

Simple, use Typemock Isolator++ (disclaimer - I work there)

Add 1 line in your test to fake/mock the Destroy

global use:

FAKE_GLOBAL(Destroy);

public statics use:

WHEN_CALLED(Third_Party::Destroy()).Ignore();

private statics use:

PRIVATE_WHEN_CALLED(Third_Party::Destroy).Ignore();

No need for extra code/ No conditional code / No different links.

Resuscitate answered 25/3, 2015 at 9:45 Comment(0)
B
0

I'm just playing here, but one approach that might work for you is to provide your own destroy function and disambiguate the call in favour of it by adding a wrapper class around the Third_Party_Lib pointer...

#include <iostream>

namespace Third_Party_Lib
{
    struct X { };
    void destroy(X*) { std::cout << "Third_Party_Lib::destroy()\n"; }
}

template <typename T>
struct Wrap
{
    Wrap(const T& t) : t_(t) { }
    operator T&() { return t_; }
    operator const T&() const { return t_; }
    T t_;
};

namespace Mine
{

#if TEST_MODE
    // this destroy will be called because it's a better match
    // not needing Wrap::operator T&...
    void destroy(Wrap<Third_Party_Lib::X*>) { std::cout << "Mine::destroy()\n"; }
#endif

    struct Q
    {
        Q(Third_Party_Lib::X* p) : p_(p) { }
        ~Q() { destroy(Wrap<Third_Party_Lib::X*>(p_)); }
        Third_Party_Lib::X* p_;
    };
}

#if TEST_MODE    
int main()
{
    Mine::Q q(new Third_Party_Lib::X);
}
#endif
Bewray answered 1/7, 2011 at 1:30 Comment(0)
B
0

A function pointer would create a way to substitute another implementation. Furthermore, it's transparent to callers (unless they're doing something really stupid like calling sizeof(funcname)).

Bergess answered 1/7, 2011 at 1:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.