20

I have class

class CSumWnd : public CBaseWnd
{

 private:
 bool MethodA()
}

Please can you help how to mock MethodA() without making virtual, I didn't understand the concept of hi-perf dependency injection

DerKasper
  • 167
  • 2
  • 11
Sasi
  • 203
  • 1
  • 2
  • 4
  • Your link is dead - could you update it? I'm not able to find the exact same page, maybe [this one](https://chromium.googlesource.com/external/github.com/google/googletest/+/refs/tags/release-1.8.0/googlemock/docs/CookBook.md#mocking-nonvirtual-methods) is sufficient? – Chris_128 Feb 13 '20 at 07:44

3 Answers3

20

It means you will have to templatize your production code. Using your example:

CSumWind class definition:

class CSumWnd : public CBaseWnd
{

 private:
 bool MethodA()
};

Mocked CSumWnd class definition:

class MockCSumWnd : public CBaseWnd
{

 private:
 MOCK_METHOD(MethodA, bool());
};

Production class which have to be tested with mocked class CSumWind. Now it becomes templated to provide using CSumWind class in production code and MockCSumWnd class in tests.

template <class CSumWndClass>
class TestedClass {
//...
   void useSumWnd(const CSumWndClass &a);

private:
  CSumWndClass sumWnd;
};

Instantiation of TestedClass in production:

TestedClass <CSumWnd> obj;

Instantiation of TestedClass object in test executable:

TestedClass <MockCSumWnd> testObj;
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
beduin
  • 7,943
  • 3
  • 27
  • 24
  • To keep your 'production' code clean, I find it helpful to do this: template class TestedClassTemplate { ... }, and then do typedef TestedClassTemplate TestedClass; – smehmood Apr 28 '11 at 19:28
  • 1
    See http://stackoverflow.com/q/1127918/49972 for information on the consequences of doing what you propose. – Tobias Furuholm May 26 '11 at 20:19
  • I am facing issue in `MOCK_METHOD(MethodA, bool());` https://stackoverflow.com/questions/46542373/how-to-mock-non-virtual-methods-using-googlemock?noredirect=1#comment80038793_46542373 I am getting error specified in the question above – CMouse Oct 03 '17 at 10:33
  • MOCK_METHOD should be replaced by MOCK_METHOD0 // 0 parameters. There is should be ";" after "bool MethodA()" as well. Unfortunately, the idea is still not clear for me... – Artem Bobritsky May 10 '19 at 20:18
  • Mind writing the body of your method, to make it clear what it should do ? void useSumWnd(const CSumWndClass &a); – Sold Out Jan 26 '22 at 09:14
3

If you don't want to change the existing code, here is a specific solution for VC++ I'm working on (https://github.com/mazong1123/injectorpp). The brief steps are:

  1. Leverage DbgHelp.h to retrieve all methods' symbols and memory addresses of a class. Basically it retrieves meta info from .pdb file at runtime.
  2. Compare the user input to-mock method symbol with step 1's output, get the to-mock method's memory address.
  3. Leverage windows api WriteProcessMemory to change the entry bytes of the to-mock method, that is something similar as: __asm {move eax, 1; ret}.

Let's put key code here.

  1. Retrieve methods' symbols and addresses of a class. Below is the key idea of the implementation. The full source code is availiable at https://github.com/mazong1123/injectorpp/blob/master/injectorpp/ClassResolver.cpp

    // Retrieve class symbol.
    if (SymGetTypeFromName(this->m_hProcess, modBase, className.c_str(), classSymbol) == FALSE)
    {
        throw;
    }
    
    // Get children of class - which are methods.
    DWORD numChildren = 0;
    if (SymGetTypeInfo(this->m_hProcess, classSymbol->ModBase, classSymbol->TypeIndex, TI_GET_CHILDRENCOUNT, &numChildren) == FALSE)
    {
        throw;
    }
    
    // Get methods info.
    if (SymGetTypeInfo(this->m_hProcess, classSymbol->ModBase, classSymbol->TypeIndex, TI_FINDCHILDREN, methods) == FALSE)
    {
        throw;
    }
    
    // Retrieve all methods.
    for (DWORD i = 0; i < numChildren; ++i)
    {
        ULONG curChild = methods->ChildId[i];
    
        // Resolve function.
        Function resolvedFunction;
        this->m_functionResolver->Resolve(classSymbol->ModBase, curChild, resolvedFunction);
    
        // Add the resolved function to the output.
        resolvedMethods.push_back(resolvedFunction);
    }
    
  2. Step 2 is trival. It's only text comparing and processing.

  3. How to inject the magic asm to change the method behavior: (The full source code is available at https://github.com/mazong1123/injectorpp/blob/master/injectorpp/BehaviorChanger.cpp)

    // A magic function to change the function behavior at runtime
    //
    // funcAddress - The address of the function to be changed from.
    // expectedReturnValue - The return value should be changed to.
    void BehaviorChanger::ChangeFunctionReturnValue(ULONG64 funcAddress, int expectedReturnValue)
    {
    
    
    // The purpose of this method is to change the return value
    // to what ever int value we expected.
    
    // Therefore, we just need to inject below asm to the header of specific function:
    //
    // mov eax, expectedValue
    // ret
    //
    // Above asm code tells the function to return expectedValue immediately.
    
    // Now let's prepare the asm command.
    byte asmCommand[6];
    
    // mov
    asmCommand[0] = 0xB8;
    
    // The value.
    asmCommand[1] = expectedReturnValue & 0xFF;
    asmCommand[2] = (expectedReturnValue >> 8) & 0xFF;
    asmCommand[3] = (expectedReturnValue >> 16) & 0xFF;
    asmCommand[4] = (expectedReturnValue >> 24) & 0xFF;
    
    // ret
    asmCommand[5] = 0xC3;
    
    WriteProcessMemory((HANDLE)-1, (void*)funcAddress, asmCommand, 6, 0);
    }
    
Jim Ma
  • 709
  • 5
  • 15
2

Try CppFreeMock and some others mentioned here.

Example:

string func() {
    return "Non mocked.";
}

TEST(HelloWorld, First) {
    EXPECT_CALL(*MOCKER(func), MOCK_FUNCTION()).Times(Exactly(1))
        .WillOnce(Return("Hello world."));
    EXPECT_EQ("Hello world.", func());
}
Louix
  • 31
  • 7
  • 3
    That second link for me points to a OneDrive login. Also rather than using just "here" as a description it would be better to put something more descriptive so the document can still be found if the link dies in the future. – PeterJ Sep 28 '14 at 05:02
  • I don't have enough reputation to add more than 2 links in one answer, update the link of "here", no need login any more. The full doc can be find in github. – Louix Sep 28 '14 at 14:11
  • looks like it fails to compile with the current gmock – Artem Bobritsky May 10 '19 at 16:40
  • 3
    @Louix It seems to me that you are the author of CppFreeMock. You should disclose that in the answer. – Jörn Schellhaas Jan 19 '21 at 16:15