Kasko
Kasko

Reputation: 19

Mocking variadic functions in Google Test and Google Mock

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

Answers (1)

Ari
Ari

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

Related Questions