There's a class under test which currently accepts a unique_ptr< Interface >&&
in it's constructor, to express that it wants to take single ownership of an Interface implementation. Problems arise when wanting to test this class using a mocked Interface
though: the mocking framework (HippoMocks) gives me only Interface*
which I do not own, hence cannot delete.
I had the same problem before when testing classes taking const shared_ptr< Interface >&
as arguments, but fixed that by providing a custom no-op deleter:
template< class T >
void NoDelete( T* )
{
}
//create a shared_ptr without effective deleter
template< class T >
std::shared_ptr< T > mock_shared( T* t )
{
return std::shared_ptr< T >( t, NoDelete< T > );
}
Interface* iface = mocks.GetMeAMock< Interface >();
DoStuffWithSharedPtrOfInterface( mock_shared< Interface >( iface ) );
A similar fix for unique_ptr doesn't really work out because the deleter is a template argument:
template< class T >
struct NoDelete
{
void operator ()( T* )
{
}
};
//oops this is totally useless since std::unique_ptr< T, NoDelete< T > >
//is not quite the same type as std::unique_ptr< T >
template< class T >
std::unique_ptr< T, NoDelete< T > > mock_unique( T* t )
{
return std::unique_ptr< T, NoDelete< T > >( t, NoDelete< T >() );
}
Is there a workaround for this? Or should I not be using unique_ptr here in the first place?
update I gave this a go; should work but sizeof( ptr ) is now 8, hard to tell what impact that has.
//use CustomUniquePtr::type instead of uniqe_ptr
template< class T >
struct CustomUniquePtr
{
typedef typename std::unique_ptr< T, void (*) ( T* ) > type;
}
//use everywhere
template< class T >
CustomUniquePtr< T >::type make_unique( T* p )
{
return CustomUniquePtr< T >::type( p, Delete< T > );
}
//use when mocking, doe not delete p!
template< class T >
CustomUniquePtr< T >::type mock_unique( T* p )
{
return CustomUniquePtr< T >::type( p, NoDelete< T > );
}
shared_ptr
stores its deleter on the heap along with the other bookkeeping data (refcount etc.); unique_ptr
has no heap overhead so the deleter has to be stored in the object and becomes part of the type.
You could template the constructor on Deleter
and convert the unique_ptr
to a shared_ptr
to erase the deleter type.
Better (depending on the size of the interface) would be to provide a proxy Interface
object that forwards to the mocked Interface *
.
I can think of several options, in no particular order:
In the test code (and only in the test code, you don't want this in your application) specialize default_delete<Interface>
to not actually delete anything. This will then mean that unique_ptr<Interface>
never deletes the object it owns, which may not be desirable if you have unique_ptr<Interface>
objects that should delete the owned objects even in the tests.
Create an implementation of Interface
that forwards everything to an instance provided to its constructor. You could then dynamically allocate an instance in the tests that forwards to the mock-framework-provided interface.
Change the mock framework to dynamically allocate the interface so it can be deleted.
Change your code to use a std::shared_ptr
so you can pass a custom deleter. This loses the "unique ownership" property though.
Change your code to use a custom smart pointer that deletes or not depending on construction parameters. This could just be a wrapper around a unique_ptr
, with a "delete or not" flag. In assignments/destruction calls if the flag is set to "do not delete" then just call release()
on the wrapped unique_ptr
rather than allowing it to delete the object. A custom pointer type has less familiarity to users than a standard one, and the flag will take up space.
Use a type generator like you have in the "update", so everywhere says CustomUniquePtr<T>::type
, and then have the type generator add the deleter. The downside here is that the user code must know about the deleter to create new instances of the type from a raw pointer. This means that user code cannot easily create an implementation of your Interface
(even a simple one that just logs calls and forwards them on) without also knowing about the deleter.
There may of course be other options.
Hippomock already provides a solution to this problem. If you have an interface with a virtual destructor then all you need to do is to register an expectation for the destructor. The mock is not destroyed by a call to its destructor as it is a mock destructor, but an expectation for the call to the destructor must be set.
MockRepository mocks;
// create the mock
std::unique_ptr<IFoo> foo( mocks.Mock<IFoo>() );
// register the expectation for the destructor
mocks.ExpectCallDestructor( foo.get() );
// call to mocks destructor ok, mock not destroyed
foo.reset( nullptr );
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With