Search code examples
c++unit-testingtemplatescircular-dependencygooglemock

*** has not been declared (though it is)


That is a part of a larger project, but looks like this files cause the problem.

StreamImpl.hpp

class StreamImpl {
...
    template <typename Type>
    StreamImpl &operator<<(T &&out) noexcept;
...
};

#include "StreamTemplateImpl.cpp"

StreamTemplateImpl.cpp

// Templates implementation needed for test purposes
#include "StreamImpl.hpp"
#include "MockStreamImpl.hpp"

using ::utils::Dispatcher;
using ::mock::MockStreamImpl; // Error here

template <typename Type>
StreamImpl &operator<<(T &&out) noexcept
{
    return Dispatcher<MockStreamImpl>::Instance().Get()->OutOperator(out);
}

MockStreamImpl.hpp

#include "StreamImpl.hpp"
namespace mock {
using ::StreamImpl;
class MockStreamImpl {
   MOCK_METHOD(...); // Gmock methods
};
}  // namespace mock

Helper class to avoid code duplication:

Fixture.hpp

#include "MockStreamImpl.hpp"

namespace fixture {
using ::mock::MockStreamImpl;
using ::utils::Dispatcher;
using ::testing::Test;

class UtStream : public Test {
   // Class for setting up mocks and common test data which is used as fixture in tests
   
    StreamImpl kDefaultStream_{};
}
}  // namespace fixture

And file with test cases, which uses StreamImpl mocks:

Tests.cpp

#include "Fixture.hpp"
... // Test cases

CMakeLists.txt

add_unit_test(
    TEST_NAME
        test_unit
    TEST_SOURCES
        unit_test_source_file.cpp
        ${SOURCES}/file_to_test.cpp
        ${MOCKS}/fake_stream_impl.cpp // source file with fake StreamImpl functions  implementations which redirects to mocks
    INCLUDE_DIRS
        ${INCLUDES}
        ${MOCKS}
        ${UTILS}
        ${FAKE_STREAM_IMPL_TEMPLATES} 
    LIBS
        gmock
        gtest
        gtest_main
)

And when I try to compile this I got

In file included from /path/src/StreamImpl.hpp:281:0,
                 from /path/test/unit/mocks/MockStreamImpl.hpp:23,
                 from /path/test/unit/mocks/Fixture.hpp:24,
                 from /path/test/unit/unit_test_source_file.cpp:23:                                                                                                       
    /path/test/unit/mocks/fake_templates/StreamTemplateImpl.cpp:25:18: error: '::mock' has not been declared                                                     
     using ::mock::MockStreamImpl;

All headers have guards so I'm not sure about circular dependency, but if it is, I'll be really thankful for explanation.

Also when I compile this without Fixture.hpp (Fixture.hpp content is merged into unit_test_source_file.cpp) all builds perfectly.

I'm really can't understand where is the problem and will be thankful for any help.

I saw a lot of related topics but neither seems contains answer for me.


Solution

  • You have an architectural problem, you are abstracting the different types of streams in a bad spot, that cannot easily be untangled. The mock interface is too tightly entwined with the code under test. You can hack around this, but you are better off stopping and thinking through exactly what you want to test.

    Sort of side note:

    StreamImpl is a bad idea.

    StreamImpl& operator<<(T &&out) noexcept;
    

    is non-standard and part of how you got into this problem. If you use the standard

    std::ostream & operator<<(std::ostream & strm,  T &&out) noexcept;
    

    and turn StreamImpl into a child of std::ostream you can pass in a re-write of MockStreamImpl that is ALSO a child of std::ostream and completely hide the specialization of the stream inside the stream rather than the function writing to the stream.

    Side note to the side note: Templating operator<< isn't a very good idea either. Most types don't have similar-enough rules to be printable by a generic function. This leads to goofy output or confused error messages when the << template tries to invoke itself because it's still the best match.

    What went wrong:

    If we follow the includes and perform the substitutions we can see what the compiler sees:

    #include "MockStreamImpl.hpp"
    
    namespace fixture {
    using ::mock::MockStreamImpl;
    using ::utils::Dispatcher;
    using ::testing::Test;
    
    class UtStream : public Test {
       // Class for setting up mocks and common test data which is used as fixture in tests
       
        StreamImpl kDefaultStream_{};
    }
    }  // namespace fixture
    

    leads to

    #include "StreamImpl.hpp"
    namespace mock {
    using ::StreamImpl;
    class MockStreamImpl {
       MOCK_METHOD(...); // Gmock methods
    };
    }  // namespace mock
    namespace fixture {
    using ::mock::MockStreamImpl;
    using ::utils::Dispatcher;
    using ::testing::Test;
    
    class UtStream : public Test {
       // Class for setting up mocks and common test data which is used as fixture in tests
       
        StreamImpl kDefaultStream_{};
    }
    }  // namespace fixture
    

    and then to

    class StreamImpl {
    ...
        template <typename Type>
        StreamImpl &operator<<(T &&out) noexcept;
    ...
    };
    
    #include "StreamTemplateImpl.cpp"
    
    namespace mock {
    using ::StreamImpl;
    class MockStreamImpl {
       MOCK_METHOD(...); // Gmock methods
    };
    }  // namespace mock
    namespace fixture {
    using ::mock::MockStreamImpl;
    using ::utils::Dispatcher;
    using ::testing::Test;
    
    class UtStream : public Test {
       // Class for setting up mocks and common test data which is used as fixture in tests
       
        StreamImpl kDefaultStream_{};
    }
    }  // namespace fixture
    

    and then, yawn, to

    class StreamImpl {
    ...
        template <typename Type>
        StreamImpl &operator<<(T &&out) noexcept;
    ...
    };
    
    // Templates implementation needed for test purposes
    #include "StreamImpl.hpp"
    #include "MockStreamImpl.hpp"
    
    using ::utils::Dispatcher;
    using ::mock::MockStreamImpl; // Error here
    
    template <typename Type>
    StreamImpl &operator<<(T &&out) noexcept
    {
        return Dispatcher<MockStreamImpl>::Instance().Get()->OutOperator(out);
    }
    namespace mock {
    using ::StreamImpl;
    class MockStreamImpl {
       MOCK_METHOD(...); // Gmock methods
    };
    }  // namespace mock
    namespace fixture {
    using ::mock::MockStreamImpl;
    using ::utils::Dispatcher;
    using ::testing::Test;
    
    class UtStream : public Test {
       // Class for setting up mocks and common test data which is used as fixture in tests
       
        StreamImpl kDefaultStream_{};
    }
    }  // namespace fixture
    

    and since StreamImpl.hpp and MockStreamImpl.hpp are protected by include guards we get

    class StreamImpl {
    ...
        template <typename Type>
        StreamImpl &operator<<(T &&out) noexcept;
    ...
    };
    
    // Templates implementation needed for test purposes
    
    using ::utils::Dispatcher;
    using ::mock::MockStreamImpl; // Error here
    
    template <typename Type>
    StreamImpl &operator<<(T &&out) noexcept
    {
        return Dispatcher<MockStreamImpl>::Instance().Get()->OutOperator(out);
    }
    namespace mock { //because mock isn't found by the compiler until here
    using ::StreamImpl;
    class MockStreamImpl {
       MOCK_METHOD(...); // Gmock methods
    };
    }  // namespace mock
    namespace fixture {
    using ::mock::MockStreamImpl;
    using ::utils::Dispatcher;
    using ::testing::Test;
    
    class UtStream : public Test {
       // Class for setting up mocks and common test data which is used as fixture in tests
       
        StreamImpl kDefaultStream_{};
    }
    }  // namespace fixture
    

    where we can see that MockStreamImpl is required before it is defined.