Parts
- C++ Mock Library: Part 1
- C++ Mock Library: Part 2
- C++ Mock Library: Part 3
- C++ Mock Library: Part 4
- C++ Mock Library: Part 5
- C++ Mock Library: Part 6
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());
}