When unit testing legacy code I find several (anti) patterns which prevent getting code under test. The most recurring pattern I see is the Singleton pattern – a useful pattern when not abused and overused.
In the object oriented world Singleton’s are slightly better than their evil cousin – the static method. Unfortunately I’ve seen them used to break encapsulation and “inject” dependencies in a way that sure to cause maintainability problems – you can read all about it here.
Regardless of whether you agree or not with the statement above – Singletons are also quite a headache when unit testing:
Consider the following (simplified) Singleton:
class MySingleton
{
static std::once_flag onceFlag;
static MySingleton* instance;
public:
static MySingleton* GetInstance(){
std::call_once(onceFlag, []{
instance = new MySingleton();
});
return instance;
}
virtual int MyMethod()
{
throw "exception";
};
private:
MySingleton(){}
MySingleton(const MySingleton& src){}
MySingleton& operator=(const MySingleton& rhs){}
virtual ~MySingleton(){}
};
Usually I would recommend a simple refactoring – extract to parameter: pass the “singleton” object to the method under test. This is a good first step in the process of removing knowledge from the object lifecycle from the object who uses it. Unfortunately this cannot be always done due to technical reasons or project/client reasons.
In this can I needed to give a simple solution to replace the Singleton instance with a fake object.
The solution in three simple steps:
In order to fake the Singleton I’ll need to be able to change it’s private instance and I’ll need be able to call it’s constructor.
Since the Singleton’s constructor is private in this example (usually is) I’ll need the Fake object to be a friend of the Singleton. Although this require changing the production code it’s less intrusive change than the other alternatives.
Unfortunate you cannot declare a friend class unless you have it’s declaration (header file), and since the fake object as well as my other test related code is in another exe/dll I have a compilation problem on my hand. Obviously linking my production code with my test code is not a good idea - instead I’ll add forward declaration to my Fake class.
As a big believe in single responsibility principle I’ll also add another class in order to swap the Singleton’s instance with my fake object (i.e. Accessor):
class MyFakeSingleton;
class MySingletonAccessor;
class MySingleton
{
static std::once_flag onceFlag;
static MySingleton* instance;
public:
static MySingleton* GetInstance(){
std::call_once(onceFlag, []{
instance = new MySingleton();
});
return instance;
}
virtual int MyMethod()
{
throw "exception";
};
private:
MySingleton(){}
MySingleton(const MySingleton& src){}
MySingleton& operator=(const MySingleton& rhs){}
virtual ~MySingleton(){}
friend class MyFakeSingleton;
friend class MySingletonAccessor;
};
Now that I have Singleton fixed (for better or worse) I can create the following two classes I need for my test.
A fake object using whatever black magic you usually use to create fake C++ classes (GMock, inheritance etc.).
Accessor class to swap the newly created fake (a.k.a mock) instead of the Singleton instacne – don’t forget to delete the existing instance if necessary – nobody like memory leaks in their tests.
class MySingletonAccessor
{
public:
static void Set(MySingleton* other)
{
// Execute singleton at least once
MySingleton::GetInstance();
delete MySingleton::instance;
MySingleton::instance = other;
}
};
Yep – that’s it, now I can write the following test code (which only shows that I can fake singletons):
#include "stdafx.h"
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "MySingleton.h"
TEST_CASE("I can fake a singleton!")
{
auto myFake = new MyFakeSingleton(); // fake is set to return 42
MySingletonAccessor::Set(myFake);
REQUIRE(MySingleton::GetInstance()->MyMethod() == 42);
}
And you’re good to go.
There are many ways to fake Singletons especially when using a powerful language such as C++ -- This in only one of them.
In fact in my experience the best way to handle Singletons is to not use them or at least avoid using them directly in methods (i.e. pass as parameter). Since we cannot always do that the process (and I use the term loosely) should be good enough.
Labels: C++, C++ 11, Mock objects, Tips and Tricks