Mocking variadic functions in Google Test and Google Mock
Asked Answered
N

1

1

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!
    }

Nonprofit answered 2/9, 2022 at 10:29 Comment(1)
I included source.cpp just to be able to test the static functions without make any change in the production codeNonprofit
C
1

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

Corkage answered 5/9, 2022 at 19:11 Comment(3)
Thank you Ari for this detailed answer, but as I understood this is testing that the panic is working but not the foo because we changed already the foo inputsNonprofit
is this production wrapper should be added to the production code?Nonprofit
Yes, the idea is to use the production wrapper in your production code and the test wrapper in the test.Corkage

© 2022 - 2024 — McMap. All rights reserved.