Reputation: 19
I'm new to Google test and I'm trying to write a unit test for a function that returns void and takes no arguments but the function has an if condition which checks on structure values, and I need to cover this line where any value of the structure's elements is 0 so the panic function would be called which prints an error message, As far as I know, panic is variadic function and variadic functions could not be mocked in Google test so how should I test that panic function would be called if I forced some values in my test to test the negative path of my function? source.cpp
void foo (){
if (
mystruct.version==0||
mystruct.size==0||
mystruct.start==0
)
panic("error message");
// some function calls
}
in my test file gtest_mytest.cpp I include my source.cpp as it has some static functions that I want to test in the future as well and I copied the panic function definition there as following,
#include "source.cpp"
void panic(const char *fmt, ...)
{
va_list v;
va_start(v, fmt);
vprintf(fmt, v);
va_end(v);
putchar('\n');
exit(1);
}
TEST(myTest,fooTestNegative)
{
myStruct teststr;
teststr.version = 0;
foo();
// MY Question is here how to expect that now Panic should be called!
}
Upvotes: 1
Views: 1233
Reputation: 7556
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
Upvotes: 1