Consider the following code:

class SomeInterface
{
public:
    virtual void foo() = 0;
    virtual ~SomeInterface() {}
};

class RealImplementation : public SomeInterface
{
public:
    void foo() { /* do complete stuff */}
};

class MockImplementation : public SomeInterface
{
public:
    void foo() { /* do simple stuff */ }
};

To be more concrete there is some example:

class IInjector
{
public:
    virtual bool injectDLL() const = 0;
    virtual ~IInjector() {}
};

class RealInjector : public IInjector
{
public:
    bool injectDLL() const
    {
        int pid = GetHookedProcessId();
        char name = readDllNameFromSomewhere();
        if (loadDllInSomeProcess(pid, name))
            return true;
        else
            return false;
    }
};

class Hook
{
public:
    bool hookProcess(const IInjector& injector)
    {
        return injector.injectDLL();
    }
};

Then in test code it is the common case to do something like this:

class MockInjector : public IInjector {
public:
    MOCK_METHOD0(injectDLL, bool());
};

TEST(HookTest, CanHookSomething) {
    MockInjector injector;                          
    EXPECT_CALL(injector, injectDLL()).Times(1);

    Hook hook;
    EXPECT_TRUE(hook.hookProcess(injector));
}

For verifying that injectDLL method was called we must know that hookProcess called injectDLL. But it is the implementation details of method hookProcess. So, in our test we open some implementation details. But it is very common case. So, does it ok to open some implementation details when using mocks?

2 Answers

1
Dirk Herrmann On

Unit-testing is normally applied as a white box testing technique - you know the code under test. Otherwise, you would also not be able to make statements about code coverage achieved by your tests. And, unit-test code is considered as belonging to the code, put under version control together with the code etc.. Thus, for your unit-test you don't 'disclose' implementation details - they are not hidden anyway.

However, certainly with mocking your tests become more dependent on the implementation details, which means, they are more likely to break or need maintenance. But that is just a tradeoff you may have to accept in this case.

0
Michael Karcher On

If the function specification of Hook::injectProcess is to use a user-supplied IInjector instance to inject the DLL, any implementation of injectProcess that does not invoke the injectDLL method on that user-supplied instance is surely broken. So in the example case you provide, there seems to be no break of encapsulation at all.

The example you show is a good example of dependency injection done ideal for testing with frameworks like Google Test. It allows some specific operations that are formalized in public interfaces to be mocked away by calling public functions with user-constructible mock objects.

In the general case, though, code like this might break encapsulation. Consider the function under test not to be a hookProcess method, whose clearly specified job is to inject a DLL, so you know for sure that a DLL has to be injected. You might want to test a function that should obtain some data, and currently it needs to inject some DLLs into some processes to do that, but there might be other ways in obtaining that information that involves no DLL injection or injecting different DLLs into different processes. In that case, testing for which DLLs get injected where does break the encapsulation provided by the "just get the information anyhow" specification. This case is discussed by the answer by Dirk Herrmann.