First, you should write a wrapper for mocking free functions as described here
Secondly, you should be able to use a method like this as a workaround for mocking variadic functions.
As a side note: it's very uncommon to include a cpp
file in your code. The common convention is to put only the signature of your functions in a .h
file, and then include that in your .cpp
files.
Below is my implementation using the above methods:
#include <stdarg.h> /* va_list, va_start, va_arg, va_end */
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::_;
class PanicWrapperInterface {
public:
virtual void panic(std::string fmt) = 0;
};
// Production wrapper.
class PanicWrapper : public PanicWrapperInterface {
public:
void panic(std::string fmt) {}
};
// Mock wrapper.
class MockPanicWrapper : public PanicWrapperInterface {
public:
MOCK_METHOD(void, panic, (const std::string), ());
};
// Variadic function.
// The panic function should take an extra parameter mock_interface.
// Alternatively, the extra parameter can be converted to a global variable.
extern "C" {
void panic(PanicWrapperInterface *mock_interface, const char *fmt, ...) {
if (dynamic_cast<PanicWrapper *>(mock_interface)) {
// The production implementation of panic function. Used only for
// production, not for testing.
va_list v;
va_start(v, fmt);
vprintf(fmt, v);
va_end(v);
putchar('\n');
} else {
// The mock implementation of panic function. Used only for testing.
std::string non_variadic("");
if (fmt != NULL) {
va_list args;
va_start(args, fmt);
// Get length of fmt including arguments
int nr = vsnprintf(NULL, 0, fmt, args);
va_end(args);
char buffer[nr + 1];
va_start(args, fmt);
vsnprintf(buffer, nr + 1, fmt, args);
va_end(args);
non_variadic = std::string(buffer);
}
mock_interface->panic(non_variadic);
}
}
}
// The foo function should take an extra parameter mock_interface.
// Alternatively, the extra parameter can be converted to a global variable.
void foo(PanicWrapperInterface *mock_interface, bool some_condition) {
if (some_condition) {
panic(mock_interface, "error message");
}
}
TEST(Panic, isCalled) {
MockPanicWrapper mock_interface;
EXPECT_CALL(mock_interface, panic(_)).Times(1);
foo(&mock_interface, /*some_condition=*/true);
}
TEST(Panic, isNotCalled) {
MockPanicWrapper mock_interface;
EXPECT_CALL(mock_interface, panic(_)).Times(0);
foo(&mock_interface, /*some_condition=*/false);
}
TEST(Panic, productionWrapperWorksCorrectly) {
// Use the production wrapper
PanicWrapper panic_wrapper;
testing::internal::CaptureStdout();
// This should print "error message" in the output.
foo(&panic_wrapper, /*some_condition=*/true);
std::string output = testing::internal::GetCapturedStdout();
EXPECT_EQ(output, "error message\n");
}
See a live example here: https://godbolt.org/z/xn36Y45eW
Note that alternatively, you can simplify the above code to work even without the conversion to non_variadic
:
class PanicWrapperInterface {
public:
virtual void DummyPanic() = 0;
};
// Production wrapper.
class PanicWrapper : public PanicWrapperInterface {
public:
void DummyPanic() {}
};
// Mock wrapper.
class MockPanicWrapper : public PanicWrapperInterface {
public:
MOCK_METHOD(void, DummyPanic, (), ());
};
// Variadic function.
// The panic function should take an extra parameter mock_interface.
// Alternatively, the extra parameter can be converted to a global variable.
extern "C" {
void panic(PanicWrapperInterface *mock_interface, const char *fmt, ...) {
if (dynamic_cast<PanicWrapper *>(mock_interface)) {
// The production implementation of panic function. Used only for
// production, not for testing.
va_list v;
va_start(v, fmt);
vprintf(fmt, v);
va_end(v);
putchar('\n');
} else {
mock_interface->DummyPanic();
}
}
}
// The foo function should take an extra parameter mock_interface.
// Alternatively, the extra parameter can be converted to a global variable.
void foo(PanicWrapperInterface *mock_interface, bool some_condition) {
if (some_condition) {
panic(mock_interface, "error message");
}
}
TEST(Panic, isCalled) {
MockPanicWrapper mock_interface;
EXPECT_CALL(mock_interface, DummyPanic()).Times(1);
foo(&mock_interface, /*some_condition=*/true);
}
TEST(Panic, isNotCalled) {
MockPanicWrapper mock_interface;
EXPECT_CALL(mock_interface, DummyPanic()).Times(0);
foo(&mock_interface, /*some_condition=*/false);
}
TEST(Panic, productionWrapperWorksCorrectly) {
// Use the production wrapper
PanicWrapper panic_wrapper;
testing::internal::CaptureStdout();
// This should print "error message" in the output.
foo(&panic_wrapper, /*some_condition=*/true);
std::string output = testing::internal::GetCapturedStdout();
EXPECT_EQ(output, "error message\n");
}
Live example here: https://godbolt.org/z/73vvrxcbo