0

I need to mock elements of an array in turtle mock. Unfortunately, because the turtle mock macro MOCK_BASE_CLASS adds extra "gunk", e.g. mock::object, mock::detail::base<> etc, the sizes of the base and mock types are no longer identical. Therefore, and pointer indexing fails when a pointer to a base class points to an array of the mock type, as shown below.

#define BOOST_TEST_MODULE First_TestSuite
#include <boost/test/included/unit_test.hpp>
#include <turtle/mock.hpp>
#include <iostream>

struct Foo
{
    int data = 42;
};

MOCK_BASE_CLASS(mockFoo , Foo)
{
};

BOOST_AUTO_TEST_CASE( Demo )
{
    mockFoo mf[10];
    Foo* b = mf;

    std::cout << "b[1].data = " << b[1].data << std::endl;
    BOOST_CHECK(b[1].data == 42);
    //BOOST_CHECK(sizeof(mockFoo) == sizeof(Foo));  // Also fails. This is the culprit
}

Execution output

Running 1 test case...
b[1].data = 32764
Test047b.cpp(23): error: in "Demo": check b[1].data == 42 has failed

*** 1 failure is detected in the test module "First_TestSuite"

I'd appreciate suggestions on how to solve this problem. Please note that I cannot change mocking framework and the base type has to be a pointer, so that it store a pointer to an array to the base or the mock type.


Update

The following example is closer to the problem that I encountered. Using MOCK_BASE_CLASS is practically inevitable in order to mock a virtual function. I am aware that the problem is storing an array of mockFoo as Foo. I have now found a solution, which I will post subject to feedback.

struct Foo
{
    int value = 42;
    int func(){ return value;}
    virtual void unused(){}
};

MOCK_BASE_CLASS(mockFoo , Foo)
{
    MOCK_METHOD(unused , 0 , void());
};

struct Bar
{
    Bar(Foo* f) : foo(f)
    {
    }

    Foo* foo;
};

BOOST_AUTO_TEST_CASE( Demo )
{
    mockFoo mf[10];
    Bar bar(mf); // uh-oh! 

    int value = bar.foo[1].func();
    std::cout << "bar.foo[1].func() = " << value << std::endl;
    BOOST_CHECK(42 == value);
}

Result

Running 1 test case...
bar.foo[1].func() = -960497840
Test047d.cpp(35): error: in "Demo": check 42 == value has failed

*** 1 failure is detected in the test module "First_TestSuite"
Olumide
  • 5,397
  • 10
  • 55
  • 104

2 Answers2

1

If you look at the Creation section in the documentation and scroll down a bit you’ll see that using the MOCK_BASE_CLASS macro is optional, the first alternative being to inherit from mock::object manually (it’s actually what the macro does under the hood).

And even that is not a hard requirement:

Deriving from mock::object is optional but provides the additional following benefits :

  • the object acts as a composite to verify and reset all the expectations for all its methods at once
  • logs involving the object are enhanced because configuring an expectation for a method will set the class name for all the other methods as well

Therefore in the end you can just do

struct mockFoo : Foo
{
};

However I’m not sure what you’re trying to achieve, mocking is about asserting behaviour (i.e. function calls) and usually involves some kind of polymorphism: your production code holds a pointer (or reference) to a base type, and the tests substitute a mock implementation to the production implementation.

mat007
  • 905
  • 8
  • 16
  • I am aware of this. The problem is that `MOCK_BASE_CLASS`, or replicating it, is (as far as I understand) inevitable in order to mock a virtual function in the base class. I have updated my question to reflect this. – Olumide Mar 14 '21 at 02:10
0

My suspicions were confirmed: a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type (source). Accordingly I created a wrapper/adapter which I call indexer to manage the indexing of the correct type, as shown below.

struct Foo
{
    int value = 42;
    int func(){ return value;}
    virtual void unused(){}
};

MOCK_BASE_CLASS(mockFoo , Foo)
{
    MOCK_METHOD(unused , 0 , void());
};

class FooIndexer
{
public:
    typedef Foo& (FooIndexer::*IndexerFunc)(int index);

    template<typename T>
    FooIndexer(T (&array)[10])
        : foo(array)
        , indexerFunc( &FooIndexer::indexer<T> )
    {
    }

    template<typename T>
    Foo& indexer(int index)
    {
        T* array = static_cast<T*>(foo);
        return array[index];
    }
    
    Foo& operator[](int index)
    {
        return (this->*(indexerFunc))(index);
    }

private:
    Foo* foo;
    IndexerFunc indexerFunc = nullptr;
};

struct Bar
{
    template<typename T>
    Bar(T (&f)[10]) : foo(f)
    {
    }

    FooIndexer foo;
};

BOOST_AUTO_TEST_CASE( Demo )
{
    mockFoo mf[10];
    Bar bar(mf);

    int value = bar.foo[1].func();
    std::cout << "bar.foo[1].func() = " << value << std::endl;
    BOOST_CHECK(42 == value);
}

Execution output

Running 1 test case...
bar.foo[1].func() = 42

*** No errors detected
Olumide
  • 5,397
  • 10
  • 55
  • 104