How Modern CMake ease way you mock in C++

Mocking again, as a “response” to my post about how easy is mocking in python. So this is my recommendation, of how to perform mocking in C++, reduce impact of testing code to a production code, and keep clean interfaces (everything in contradiction to dependency injection).

You need to know CMake

In case you don’t know how CMake populates target properties, please go to my previous post.

Goal

Goal is to link your production code like this:

add_executable(sanitize_file main.cpp)
target_link_libraries(sanitize_file
    FileSanitizer::Lib)

Where FileSanitizer::Lib consist of:

set (file_sanitizer_sources
        file_sanitizer.cpp 
        file_reader.cpp
    )
add_library(file_sanitizer OBJECT ${file_sanitizer_sources})
add_library(FileSanitizer::Lib ALIAS ${PROJECT_NAME})

and you link your testing target like this:

add_executable(file_sanitizer_test test.cpp)
target_link_libraries(file_sanitizer_test
    FileSanitizer::Lib::file_sanitizer # this is implementation
    GTest::GTest
    Mock::FileReader # this is mock
    GTest::Main 
    Threads::Threads
    )

So to link out test target partially with a production code and partially with a mocked implementation we need to do 2 things: 1) Define interface targets per compilation unit, 2) create a mock and declare target for it

Interface targets per compilation unit.

To create targets for file_sanitizer.cpp and file_reader.cpp we need will will create a little CMake Function:

function(DeclareInterfaceTargets prefix sources)
    foreach(source_file ${${sources}})                                                                                  
       get_filename_component(file_name ${source_file} NAME_WE)                                                        
       set(target_name "alias_${file_name}_target")                                                                    
       add_library(${target_name} INTERFACE)                                                                           
       target_sources(${target_name} INTERFACE ${source_file})                                                         
      target_include_directories(${target_name} INTERFACE         
      add_library(${prefix}::${file_name} ALIAS ${target_name})                                                   
    endforeach()
endfunction()

Function takes a list of sources (cpp files only), declares INTERFACE library for each, and sets an ALIAS. So we have targets like FileSanitizer::Lib::file_sanitizer and FileSanitizer::Lib::file_reader. Now we can use those targets to link our test choosing what is to be tested, and what is to be mocked.

Creating Mock

We need to solve 2 problems. 1) Code that we want to replace is instantiated by a production code (unless you are doing dependency injection) so by default you do not have any access to our mock, and still we want to set EXPECT on a mock. So we will use a singleton pattern (i know …) to get the access, remember that you need to instantiate mock before running SUT (System Under Test).

TEST(MyTest, call_mock){
    auto mock = FileReaderMock::get_instance();
    EXPECT_CALL(*mock, read()).WillOnce(Return("hello"));
    EXPECT_TRUE ( file_sanitizer::sanitize_file(""));
    FileReaderMock::remove_instance();
}

2) You need to solve conflicting include directories. General approach in mocking is to mock the header (as in gmock tutorial), this mock will be included with by a source file that is using mocked part. For this you would need to replace production target_include_directories with a mocked one. Unfortunately there is not just one file in that folder, you have also production headers in this same place, and you do not want to replace them, you need to copy them into testing target_include_directories. So I do not recommend this way.
Instead I propose to leave production header as is, and use a fake implementation (so a cpp file) to proxy control flow into the mock, like this:

#include <file_reader.h>
#include <string>
#include <gmock/gmock.h>
#include <file_reader_mock.h> # actual mock 
FileReader::FileReader(const std::string& ){
}
std::string FileReader::read(){
    return FileReaderMock::get_instance()->read();
}

and mock itself:

#pragma once
#include <gmock/gmock.h>
class FileReaderMock;
static FileReaderMock* instance = nullptr;
class FileReaderMock{
    public:
    static FileReaderMock* get_instance(){
        if (instance == nullptr)
            instance = new FileReaderMock();
        return instance;
    }
    static void remove_instance(){
        if (instance != nullptr)
            delete instance;
            instance = nullptr;
    }
    MOCK_METHOD0(read, std::string());
};

Foot note.

This approach changes a bit meaning what is a testable code and what is not, … but also reduces impact of a testing to a production code. For complete code example please check modern_cmake_example on my GitHub.

Best regards
Michał

Leave a comment