Skip to main content
3 of 6
added 53 characters in body
Loki Astari
  • 97.7k
  • 5
  • 126
  • 341

C++ Mock Library: Part 2

Parts

Removing redundant MOCK_SYS usage

After writing a bunch of unit test we see a pattern emerge. The lambda's we are using don't actually do much and most of the time simply return the same value. We only need to specialize them when testing error cases (most of the time they return the same thing).

So part two is about building an object that provides a default implementation for all the mocks.

Building: test/MockHeaderInclude.h

We build the following as part of the build files.

test/MockHeaderInclude.h: .FORCE
    buildMockHeaderInclude

Then the script that does the work:

#!/bin/bash

IFS='%' read -r -d '' -a fileTemplate <<PREFIX
#ifndef THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE
#define THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE

#include <functional>

// Please add includes for all mocked libraries here.
// PART-1-Start
%
// PART-1-End
namespace ThorsAnvil::BuildTools::Mock
{

// Please define all FuncType_<XXX> here
// There should be one for each MOCK_TFUNC you use in the code.
// The make files will provide the declaration but these need to be filled in by
// the developer and committed to source control
// PART-2-Start
%
// PART-2-End
// This default implementation of overridden functions
// Please provide a lambda for the implementation
// When you add/remove a MOCK_FUNC or MOCK_TFUNC to the source
// This list will be updated.

}

#include "coverage/MockHeaders.h"

namespace ThorsAnvil::BuildTools::Mock
{
class MockAllDefaultFunctions
{
    int version;
// PART-3-Start
%
// PART-3-End

    public:
        MockAllDefaultFunctions()
            : version(2)
// PART-4-Start
%
// PART-4-End
        {}
};


}

#endif
PREFIX

function copyPart {

    local fileName=$1
    local section=$2

    cat ${fileName} | awk 'BEGIN {InSection=0;} /'PART-${section}'-Start/ {InSection=1;next;} /PART-'${section}'-End/ {InSection=0} {if (InSection == 1){print}}'
}

function getFunctions {
    perl -ne '/MOCK_(T?)FUNC\([ \t]*([^\) \t]*)/ and print "$2 $1\n"' * | sort | uniq
}

function buildFuncType {
    local fileName=$1

    while read line; do
        split=(${line})
        name=${split[0]}
        type=${split[1]}

        if [[ "${type}" == "T" ]]; then
            find=$(grep "using FuncType_${name}[ \t]*=" "${fileName}")
            if [[ "${find}" == "" ]]; then
                echo "using FuncType_${name}        = /* Add function type info here */;"
            fi
        fi
    done < <(getFunctions)
}

function buildMEMBER {
    local fileName=$1

    while read line; do
        split=(${line})
        name=${split[0]}
        type=${split[1]}

        find=$(grep "MOCK_${type}MEMBER(${name});" "${fileName}")
        if [[ "${find}" == "" ]]; then
            echo "    MOCK_${type}MEMBER(${name});"
        fi
    done < <(getFunctions)
}

function buildPARAM {
    local fileName=$1

    while read line; do
        split=(${line})
        name=${split[0]}

        find=$(grep " MOCK_PARAM(${name}," "${fileName}")
        if [[ "${find}" == "" ]]; then
            echo "            , MOCK_PARAM(${name},     []( Add expected parameters here ){return Add default value here;}),"
        fi
    done < <(getFunctions)
}

function createFile {
    local fileName=$1

    echo "${fileTemplate[0]}"
    copyPart "${fileName}" 1
    echo "${fileTemplate[1]}"
    copyPart "${fileName}" 2
    buildFuncType ${fileName}
    echo "${fileTemplate[2]}"
    copyPart "${fileName}" 3
    buildMEMBER ${fileName}
    echo "${fileTemplate[3]}"
    copyPart "${fileName}" 4
    buildPARAM ${fileName}
    echo "${fileTemplate[4]}"
}

function buildFile {

    local fileName=$1

    if [[ -e test ]]; then
        createFile ${fileName} > ${fileName}.tmp
        if [[ -e ${fileName} ]]; then
            diff ${fileName}.tmp ${fileName}
            if [[ $? == 1 ]]; then
                echo "ReBuilt: ${fileName}"
                mv ${fileName}.tmp ${fileName}
            else                                                                                        \
                rm ${fileName}.tmp
            fi
        else                                                                                            \
            echo "Built: ${fileName}"
            mv ${fileName}.tmp ${fileName}
        fi
    fi
}

buildFile test/MockHeaderInclude.h

This then generates the following file:

#ifndef THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE
#define THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE

#include <functional>

// Please add includes for all mocked libraries here.
// PART-1-Start
// PART-1-End
namespace ThorsAnvil::BuildTools::Mock
{

// Please define all FuncType_<XXX> here
// There should be one for each MOCK_TFUNC you use in the code.
// The make files will provide the declaration but these need to be filled in by
// the developer and committed to source control
// PART-2-Start
// PART-2-End
// This default implementation of overridden functions
// Please provide a lambda for the implementation
// When you add/remove a MOCK_FUNC or MOCK_TFUNC to the source
// This list will be updated.

}

#include "coverage/MockHeaders.h"

namespace ThorsAnvil::BuildTools::Mock
{
class MockAllDefaultFunctions
{
    int version;
// PART-3-Start
    MOCK_MEMBER(close);
    MOCK_MEMBER(connect);
    MOCK_TMEMBER(fcntl);
    MOCK_MEMBER(gethostbyname);
    MOCK_TMEMBER(open);
    MOCK_MEMBER(pipe);
    MOCK_MEMBER(read);
    MOCK_MEMBER(shutdown);
    MOCK_MEMBER(socket);
    MOCK_MEMBER(write);
// PART-3-End

    public:
        MockAllDefaultFunctions()
            : version(2)
// PART-4-Start
            , MOCK_PARAM(close,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(connect,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(fcntl,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(gethostbyname,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(open,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(pipe,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(read,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(shutdown,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(socket,     []( Add expected parameters here ){return Add default value here;}),
            , MOCK_PARAM(write,     []( Add expected parameters here ){return Add default value here;}),
// PART-4-End
        {}
};


}

#endif

Which I then defined like this:

#ifndef THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE
#define THORSANVIl_THORS_SOCKET_MOCK_HEADER_INCLUDE

#include <functional>

// Please add includes for all mocked libraries here.
// PART-1-Start
#include <fcntl.h>
#include <netdb.h>
#include "ThorsSocketConfig.h"

// PART-1-End
namespace ThorsAnvil::BuildTools::Mock
{

// Please define all FuncType_<XXX> here
// There should be one for each MOCK_TFUNC you use in the code.
// The make files will provide the declaration but these need to be filled in by
// the developer and committed to source control
// PART-2-Start
using FuncType_open     = int(const char*, int, unsigned short);
using FuncType_fcntl    = int(int, int, int);

// PART-2-End
// This default implementation of overridden functions
// Please provide a lambda for the implementation
// When you add/remove a MOCK_FUNC or MOCK_TFUNC to the source
// This list will be updated.

}

#include "coverage/MockHeaders.h"

namespace ThorsAnvil::BuildTools::Mock
{
class MockAllDefaultFunctions
{
    int version;
// PART-3-Start
    std::function<hostent*(const char*)> getHostByNameMock =[]  (char const*) {
        static char* addrList[] = {""};
        static hostent result {.h_length=1, .h_addr_list=addrList};
        return &result;
    };

    MOCK_MEMBER(read);
    MOCK_MEMBER(write);
    MOCK_TMEMBER(open);
    MOCK_MEMBER(close);
    MOCK_TMEMBER(fcntl);
    MOCK_MEMBER(pipe);
    MOCK_MEMBER(socket);
    MOCK_MEMBER(gethostbyname);
    MOCK_MEMBER(connect);
    MOCK_MEMBER(shutdown);

// PART-3-End

    public:
        MockAllDefaultFunctions()
            : version(2)
// PART-4-Start
            , MOCK_PARAM(read,                                  [ ](int, void*, ssize_t size)           {return size;})
            , MOCK_PARAM(write,                                 [ ](int, void const*, ssize_t size)     {return size;})
            , MOCK_PARAM(open,                                  [ ](char const*, int, int)              {return 12;})
            , MOCK_PARAM(close,                                 [ ](int)                                {return 0;})
            , MOCK_PARAM(fcntl,                                 [ ](int, int, int)                      {return 0;})
            , MOCK_PARAM(pipe,                                  [ ](int* p)                             {p[0] = 12; p[1] =13;return 0;})
            , MOCK_PARAM(socket,                                [ ](int, int, int)                      {return 12;})
            , MOCK_PARAM(gethostbyname,                         std::move(getHostByNameMock))
            , MOCK_PARAM(connect,                               [ ](int, sockaddr const*, unsigned int) {return 0;})
            , MOCK_PARAM(shutdown,                              [ ](int, int)                           {return 0;})
// PART-4-End
        {}
};


}

#endif

Now subsequent calls never change this file (anything I have previously defined is left unchanged). So this file is checked into source control.

If I had more MOCK_FUNC() macros to my source then these will of course be added to this file.

Helper Macros

#define MOCK_MEMBER_EXPAND(type, func)                      ThorsAnvil::BuildTools::Mock::MockOutFunction<type> mockOutFunction_ ## func
#define MOCK_TMEMBER(func)                                  MOCK_MEMBER_EXPAND(ThorsAnvil::BuildTools::Mock::FuncType_ ## func, func)
#define MOCK_MEMBER(func)                                   MOCK_MEMBER_EXPAND(decltype(::func), func)
#define MOCK_PARAM_EXPAND(func, name, lambda)               mockOutFunction_ ## func(name, lambda)
#define MOCK_PARAM(func, lambda)                            MOCK_PARAM_EXPAND(func, MOCK_BUILD_MOCK_NAME(func), lambda)

So basically each member declaration expands to (using socket as example):

// MOCK_MEMBER(socket)
ThorsAnvil::BuildTools::Mock::MockOutFunction<decltype(::socket)> mockOutFunction_socket;

With the constructor initializing like this:

// MOCK_PARAM(socket, [](int, int, int){return 12;})
, mockOutFunction_socket("socket", [](int, int, int){return 12;})

MockOutFunction

This was previously defined in C++ Mock Library: Part 1

Usage in Tests

TEST(UniTestBloc, MyUnitTest)
{
    MockAllDefaultFunctions   defaultMocks;

    auto action = [](){
        ThorsSocket::Socket socket("www.google.com", 80);
        // Do tests
    };
    ASSERT_NO_THROW(action());
}
Loki Astari
  • 97.7k
  • 5
  • 126
  • 341