Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing, mocking and unique_ptr

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 > );
}
like image 622
stijn Avatar asked Jun 14 '12 15:06

stijn


3 Answers

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 *.

like image 160
ecatmur Avatar answered Oct 19 '22 00:10

ecatmur


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.

like image 33
Anthony Williams Avatar answered Oct 19 '22 00:10

Anthony Williams


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 );
like image 36
Thomas Avatar answered Oct 19 '22 00:10

Thomas