reshape the management interface
now using static functions; which simplifies building a scoped object to install a mock automatically within unit tests.
This commit is contained in:
parent
7000a40602
commit
78c7036678
3 changed files with 115 additions and 22 deletions
|
|
@ -38,6 +38,13 @@ This code is heavily inspired by
|
|||
#include "lib/sync-classlock.hpp"
|
||||
#include "lib/dependency-factory.hpp"
|
||||
|
||||
#include "lib/meta/duck-detector.hpp" ////TODO move in separate header
|
||||
|
||||
#include <boost/noncopyable.hpp> ////TODO move in separate header
|
||||
#include <boost/static_assert.hpp> ////TODO move in separate header
|
||||
#include <boost/utility/enable_if.hpp> ////TODO move in separate header
|
||||
|
||||
|
||||
namespace lib {
|
||||
|
||||
/**
|
||||
|
|
@ -86,7 +93,20 @@ namespace lib {
|
|||
return *instance;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
typedef DependencyFactory::InstanceConstructor Constructor;
|
||||
|
||||
Depend (Constructor ctor = buildSingleton<SI>())
|
||||
{
|
||||
factory.installConstructorFunction (ctor);
|
||||
}
|
||||
|
||||
// standard copy operations applicable
|
||||
|
||||
|
||||
/* === Management / Test support interface === */
|
||||
|
||||
static void
|
||||
shutdown()
|
||||
{
|
||||
SyncLock guard;
|
||||
|
|
@ -95,7 +115,7 @@ namespace lib {
|
|||
instance = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
injectReplacement (SI* mock)
|
||||
{
|
||||
REQUIRE (mock);
|
||||
|
|
@ -106,22 +126,12 @@ namespace lib {
|
|||
instance = mock;
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
dropReplacement()
|
||||
{
|
||||
SyncLock guard;
|
||||
factory.restore (instance); // EX_FREE
|
||||
factory.restore (instance); // EX_FREE
|
||||
}
|
||||
|
||||
|
||||
typedef DependencyFactory::InstanceConstructor Constructor;
|
||||
|
||||
Depend (Constructor ctor = buildSingleton<SI>())
|
||||
{
|
||||
factory.installConstructorFunction (ctor);
|
||||
}
|
||||
|
||||
// standard copy operations applicable
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -135,5 +145,76 @@ namespace lib {
|
|||
|
||||
|
||||
|
||||
namespace { ///< details: inject a mock automatically in place of a singleton
|
||||
|
||||
using boost::enable_if;
|
||||
using lib::meta::Yes_t;
|
||||
using lib::meta::No_t;
|
||||
|
||||
/**
|
||||
* Metafunction: does the Type in question
|
||||
* give us a clue about what service interface to use?
|
||||
*/
|
||||
template<class MOCK>
|
||||
class defines_ServiceInterface
|
||||
{
|
||||
META_DETECT_NESTED (ServiceInterface);
|
||||
|
||||
public:
|
||||
enum{ value = HasNested_ServiceInterface<MOCK>::value
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Policy-Trait: determine the access point to install the mock.
|
||||
* @note either the mock service implementation needs to provide
|
||||
* explicitly a typedef for the ServiceInterface, or we're
|
||||
* just creating a separate new instance of the singleton service,
|
||||
* while shadowing (but not destroying) the original instance.
|
||||
*/
|
||||
template<class I, class YES =void>
|
||||
struct ServiceInterface
|
||||
{
|
||||
typedef I Type;
|
||||
};
|
||||
|
||||
|
||||
template<class MOCK>
|
||||
struct ServiceInterface<MOCK, typename enable_if< defines_ServiceInterface<MOCK> >::type>
|
||||
{
|
||||
typedef typename MOCK::ServiceInterface Type;
|
||||
};
|
||||
|
||||
}//(End) mock injection details
|
||||
|
||||
|
||||
/**
|
||||
* Scoped object for installing/deinstalling a mocked service automatically.
|
||||
* Placing a suitably specialised instance of this template into a local scope
|
||||
* will inject the corresponding mock installation and remove it when the
|
||||
* control flow leaves this scope.
|
||||
* @param TYPE the concrete mock implementation type to inject. It needs to
|
||||
* be default constructible. If TYPE is a subclass of the service interface,
|
||||
* it needs to expose a typedef \c ServiceInterface
|
||||
*/
|
||||
template<class TYPE>
|
||||
struct Use4Test
|
||||
: boost::noncopyable
|
||||
{
|
||||
typedef typename ServiceInterface<TYPE>::Type Interface;
|
||||
|
||||
Use4Test()
|
||||
{
|
||||
Depend<Interface>::injectReplacement (new TYPE);
|
||||
}
|
||||
|
||||
~Use4Test()
|
||||
{
|
||||
Depend<Interface>::dropReplacement();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace lib
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
namespace lib {
|
||||
|
||||
/**
|
||||
* Factory to generate and manage service objects classified by type.
|
||||
* Factory to generate and manage service objects classified by type.
|
||||
*/
|
||||
class DependencyFactory
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ namespace test{
|
|||
uint id1 = accessor1().instanceID_;
|
||||
CHECK (id1 == accessor2().instanceID_);
|
||||
|
||||
accessor1.shutdown();
|
||||
Depend<Sub>::shutdown();
|
||||
|
||||
Sub & o2 = accessor2();
|
||||
uint id2 = accessor2().instanceID_;
|
||||
|
|
@ -154,7 +154,7 @@ namespace test{
|
|||
SubSub& oSub = otherSpecialAccessor();
|
||||
CHECK ( INSTANCEOF (SubSubSub, &oSub));
|
||||
|
||||
otherSpecialAccessor.shutdown();
|
||||
Depend<SubSub>::shutdown();
|
||||
|
||||
Depend<SubSub> yetAnotherSpecialAccessor;
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ namespace test{
|
|||
void
|
||||
verify_customFactory()
|
||||
{
|
||||
Depend<SubSubSub> customisedAccessor(useFactory (&customFactoryFunction));
|
||||
Depend<SubSubSub> customisedAccessor(&customFactoryFunction);
|
||||
Depend<SubSub> otherSpecialAccessor;
|
||||
|
||||
SubSub& oSub = otherSpecialAccessor();
|
||||
|
|
@ -180,7 +180,7 @@ namespace test{
|
|||
CHECK (MAX_ID + 10 == oSubS.instanceID_);
|
||||
}
|
||||
|
||||
static SubSubSub*
|
||||
static void*
|
||||
customFactoryFunction (void)
|
||||
{
|
||||
SubSubSub* newObject = static_cast<SubSubSub*> (DependencyFactory::createSingletonInstance<SubSubSub>());
|
||||
|
|
@ -193,11 +193,13 @@ namespace test{
|
|||
void
|
||||
verify_temporaryReplacement()
|
||||
{
|
||||
Depend<Sub> genericAccessor;
|
||||
typedef Depend<Sub> GenericAccessor;
|
||||
|
||||
GenericAccessor genericAccessor;
|
||||
Sub& original = genericAccessor();
|
||||
uint oID = original.instanceID_;
|
||||
|
||||
genericAccessor.injectReplacement (new SubSubSub);
|
||||
GenericAccessor::injectReplacement (new SubSubSub);
|
||||
|
||||
Sub& replacement = genericAccessor();
|
||||
uint repID = replacement.instanceID_;
|
||||
|
|
@ -215,7 +217,7 @@ namespace test{
|
|||
CHECK(!isSameObject (replacement, special() ));
|
||||
CHECK(!isSameObject (replacement, custom() ));
|
||||
|
||||
genericAccessor.dropReplacement();
|
||||
GenericAccessor::dropReplacement();
|
||||
|
||||
Sub& nextFetch = genericAccessor();
|
||||
CHECK (isSameObject (original, nextFetch));
|
||||
|
|
@ -248,6 +250,16 @@ namespace test{
|
|||
CHECK (repID == otherAccess.instanceID_);
|
||||
CHECK (repID == replacement.instanceID_);
|
||||
CHECK ( oID == original.instanceID_);
|
||||
|
||||
// verify the instrumentation indeed targeted the generic accessor,
|
||||
// and *not* an accessor of the sub type, i.e Depend<SubSub>
|
||||
Depend<SubSub> genericSubTypeAccessor;
|
||||
SubSub& subTypeAccess = genericSubTypeAccessor();
|
||||
CHECK ( INSTANCEOF (SubSub, &subTypeAccess));
|
||||
CHECK (!isSameObject (replacement, subTypeAccess));
|
||||
CHECK (!isSameObject (original, subTypeAccess));
|
||||
CHECK (repID != subTypeAccess.instanceID_);
|
||||
CHECK ( oID != subTypeAccess.instanceID_);
|
||||
}
|
||||
|
||||
Sub& nextFetch = genericAccessor();
|
||||
|
|
|
|||
Loading…
Reference in a new issue