Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What alternatives are there to template or link seam dependency injection for testing non-virtual methods?

I am trying to test code that has many dependencies on code which cannot be changed and often does not use virtual methods. It is also a high-performance scenario, so I can imagine some places in our own code that we'd not want tot use virtual methods. The non-virtual methods are important to the test scenarios, so I wish to mock them.

As I understand it, there are two main options:

  1. Template dependency injection: Google calls this hi-perf dependency injection. The mock is no longer a derived class of the dependency, which is replaced with a template class. It is instantiated as the original dependency's class for production and the mock class for test.

  2. Link seams: name the test class the same as the production class and use linker tricks to substitute it for the production implementation when linking tests.

I've used 1. successfully but it can quickly get out of control: I will be templatizing most of the code base to deal with the dependencies' non-virtual methods. 2. seems rather inelegant and - perhaps more importantly - will be even more unfamiliar to most people.

Are there alternative approaches? What do people do when depending on a large library of non-virtual code that they don't control?

like image 944
Sam Brightman Avatar asked Jun 15 '17 16:06

Sam Brightman


1 Answers

There is one other approach I've seen before: Create a thin wrapper class with only inline member functions that just forward to the external library, but make them conditionally virtual via a preprocessor macro. Then in the build used for testing you define the macro so that you can overwrite the member functions in a mock while in the production build you don't define the macro so that the compiler can inline the library calls as the definitions of all member functions of the wrapper are visible.

#ifdef TEST_BUILD
#define VIRTUAL virtual
#else
#define VIRTUAL
#endif

class library_wrapper {
public:
    VIRTUAL void foo(int i) {
        ::externallib::foo(i);
    }
};

class library_wrapper_mock : public library_wrapper {
public:
    MOCK_METHOD1(foo, void(int));
}

Of course this means you can no longer test the build you're shipping but that is the tradeoff for the performance gain of not using virtual functions in the production build.

Another alternative is to use a different mocking library such as Hippomocks which can also mock free functions (as long a they're not inlined) by using platform-specific trickery to overwrite instructions in the "real" function to jump to a mocked version.

like image 55
Corristo Avatar answered Nov 08 '22 01:11

Corristo