TL;DR: You can use GMock to add mocking capability to your Microsoft native c++ unit tests. See my answer below for details.
I want to start adding mocks to my existing set of native unit tests. The tests are written using Microsoft's CppUnitTestFramework
framework, which doesn't have support for mocking. I don't really want to convert the entire test suite to another framework just to add a few mocks.
Google's GMock framework seems to provide everything I need and the documentation suggests it can be used with frameworks other than gtest
. So using advice from blog posts like this one, I created a couple of unit tests.
TEST_MODULE_INITIALIZE(ModuleInitialize)
{
// Enable google mock
GTEST_FLAG(throw_on_failure) = true;
int argc = 0;
TCHAR **argv = nullptr;
InitGoogleMock(&argc, argv);
}
TEST_CLASS(GMockTests)
{
public:
MockTestClass _mockObj;
TEST_METHOD(Method1_ParamIsOne_Method2CalledWithOne)
{
EXPECT_CALL(_mockObj, Method2(1)).Times(1);
_mockObj.Method1(1);
}
TEST_METHOD(Method1_ParamIsZero_IntentionallyFail)
{
// Expectation will fail
EXPECT_CALL(_mockObj, Method2(1)).Times(1);
_mockObj.Method1(0);
}
};
The results are less than satisfactory. The expectations do work (first method passes), but if any expectation fails the entire run is aborted with only the following unhelpful message in the test output:
[3/27/2019 11:39:17 AM Error] The active test run was aborted. Reason:
[3/27/2019 11:39:17 AM Informational] ========== Run test finished: 0 run (0:00:22.3042194) ==========
The Visual Studio Test Explorer window doesn't indicate what the problem is either. It just shows that one test succeeded and the other one didn't run:
So what I'm looking for from this integration is:
I was able to get GMock
working correctly with the CppUnitTestFramework
by creating a custom TestEventListener
. I then created a simple set of interface functions to make it easier to work with.
I used the gmock 1.7.0 NuGet package to install the GMock framework into my project, and added the two files shown below.
GMockUtils.h
#pragma once
#include <CppUnitTest.h>
#include <gmock/gmock.h>
namespace testing {
namespace GMockUtils {
// Call once per test class or module to set up everything needed by GoogleMock.
void InitGoogleMock();
// Call once per test method to check for GoogleMock messages.
void CheckGoogleMock();
}
}
GMockUtils.cpp
#include "stdafx.h"
#include "GMockUtils.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace testing {
namespace GMockUtils {
namespace {
// Test event listener for use with CppUnitTestFramework
class CppUnitTestReporter : public EmptyTestEventListener
{
public:
CppUnitTestReporter() : _failed(false)
{
}
// Helper for converting std::string to std::wstring
std::wstring to_wstring(const std::string& str) const
{
std::wstring output;
std::copy(str.begin(), str.end(), std::back_inserter(output));
return output;
}
// Called after a failed assertion or a SUCCEED() invocation.
void OnTestPartResult(const ::testing::TestPartResult& result) override
{
// Log this result to the CppUnitTestFramework output
Logger::WriteMessage(result.summary());
// Note: You cannot do an Assert directly from a listener, so we
// just store the messages until CheckGoogleMock() is called.
if (result.failed())
{
_failed = true;
// Append this result to the running summary
_failedSummary += result.message();
_failedSummary += "\n";
}
}
// Clear any previous failures
void ResetFailures()
{
_failed = false;
_failedSummary.clear();
}
// Assert if any failures have been detected. Also resets failures.
void CheckFailures()
{
auto failed = _failed;
auto failedSummary = _failedSummary;
ResetFailures();
Assert::IsFalse(failed, to_wstring(failedSummary).c_str());
}
protected:
bool _failed;
std::string _failedSummary;
} *_listener;
}
// Initialize the Google Mock framework for use with CppUnitTestFramework
void InitGoogleMock()
{
// Avoids calling the function unnecessarily
if (_listener != nullptr)
return;
// Dummy command line parameters (could pass exe path here)
int argc = 0;
char** argv = nullptr;
// Initialize the framework
::testing::InitGoogleMock(&argc, argv);
// We don't want exceptions thrown, regardless what the doc says
GTEST_FLAG(throw_on_failure) = false;
// Remove default listener
auto &listeners = UnitTest::GetInstance()->listeners();
delete listeners.Release(listeners.default_result_printer());
// Create and install the new listener
_listener = new CppUnitTestReporter();
listeners.Append(_listener);
}
// Reset any previous failures detected by the listener
void ResetGoogleMock()
{
_listener->ResetFailures();
}
// Prints messages and asserts if any Google Mock messages are found.
void CheckGoogleMock()
{
Assert::IsNotNull(_listener, L"Google Mock framework not initialized by InitGoogleMock()");
_listener->CheckFailures();
}
}
}
I use the GMockUtils functions in unit test classes like this:
#include "GMockUtils.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace ::testing;
namespace GMockUtilsDemo
{
TEST_CLASS(GMockUtilTests)
{
public:
TEST_CLASS_INITIALIZE(ClassInitializer)
{
// IMPORTANT: This must be called before any mock object constructors
GMockUtils::InitGoogleMock();
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
// Checks for GoogleMock messages. Asserts if found.
GMockUtils::CheckGoogleMock();
}
TEST_METHOD(Method1_ParamIsOne_Method2CalledWithOne)
{
MockTestClass mockObj;
EXPECT_CALL(mockObj, Method2(1)).Times(1); // Expectation will be met
mockObj.Method1(1);
}
TEST_METHOD(Method1_ParamIsZero_IntentionallyFail)
{
MockTestClass mockObj;
EXPECT_CALL(mockObj, Method2(1)).Times(1); // Expectation will not be met
mockObj.Method1(0);
}
};
}
The Console Output
The console output now shows all GMock messages, and the run does not abort on the first test failure.
[3/27/2019 12:23:46 PM Informational] ------ Run test started ------
[3/27/2019 12:23:46 PM Informational]
Unexpected mock function call - returning directly.
Function call: Method2(0)
Google Mock tried the following 1 expectation, but it didn't match:
c:\...\gmockutilsdemo.cpp(64): EXPECT_CALL(_mockObj, Method2(1))...
Expected arg #0: is equal to 1
Actual: 0
Expected: to be called once
Actual: never called - unsatisfied and active
[3/27/2019 12:23:46 PM Informational] Actual function call count doesn't match EXPECT_CALL(_mockObj, Method2(1))...
Expected: to be called once
Actual: never called - unsatisfied and active
[3/27/2019 12:23:46 PM Informational] ========== Run test finished: 2 run (0:00:00.8631468) ==========
Test Explorer View
If I run the tests via Visual Studio Test Explorer, I can also see all GMock messages related to a particular test. It also works with the VsTest task on Azure DevOps.
Hopefully this will be useful to anyone who finds themselves in the same situation.