13 January 2008

Disposable mocks

I was writing some unit tests for a class that had a dependency on an interface that inherited from IDisposable. I'm using Rhino Mocks (one of the best mock objects library I know) to mock the dependencies of the class being tested.

The class itself implemented IDisposable also, to dispose correctly of its resources. The code for the class looked approximately like this:


using System;


namespace DisposableMock
{

public interface ISomeInterface : IDisposable
{
void DoStuff();
}

public class SomeDisposableClass : IDisposable
{
private ISomeInterface _someInterface;

public ISomeInterface SomeInterface
{
set { _someInterface = value; }
}

public void DoSomeOtherStuff()
{
_someInterface.DoStuff();
}

#region IDisposable Members

public void Dispose()
{
_someInterface.Dispose();
}

#endregion
}
}

The test fixture looked like this:


using NUnit.Framework;
using Rhino.Mocks;

namespace DisposableMock.Test
{
/// <summary>
/// A test fixture for <see cref="SomeDisposableClass"/>
/// </summary>
[TestFixture]
public class SomeDisposableClassFixture
{
private MockRepository _mocks;
private SomeDisposableClass _testee;
private ISomeInterface _someInterface;

[SetUp]
public void Setup()
{
_mocks = new MockRepository();
_testee = new SomeDisposableClass();
_someInterface = _mocks.DynamicMock<ISomeInterface>();
}

[TearDown]
public void TearDown()
{
if (_testee != null)
_testee.Dispose();
}

[Test]
public void Test()
{
// Expectations setup
_someInterface.DoStuff();
_mocks.ReplayAll();

// Replay
_testee.SomeInterface = _someInterface;
_testee.DoSomeOtherStuff();

// Expectations verification
_mocks.VerifyAll();
}
}
}

What's interesting here is the TearDown method. As a good .NET citizen, the test fixture is cleaning up after itself, and it calls the Dispose method of the object under test. Unfortunately, this makes the test fail miserably with an exception in Teardown.

The stack trace looks like this:

System.InvalidOperationException: This action is invalid when the mock object is in verified state.


 

at Rhino.Mocks.Impl.VerifiedMockState.MethodCall(IInvocation invocation, MethodInfo method, Object[] args)

at Rhino.Mocks.MockRepository.MethodCall(IInvocation invocation, Object proxy, MethodInfo method, Object[] args)

at Rhino.Mocks.Impl.RhinoInterceptor.Intercept(IInvocation invocation)

at Castle.DynamicProxy.AbstractInvocation.Proceed()

at ISomeInterfaceProxy71b077222e754186ac7f599256a49a48.Dispose()

at DisposableMock.SomeDisposableClass.Dispose() in SomeDisposableClass.cs:line 29

at DisposableMock.Test.SomeDisposableClassFixture.TearDown() in SomeDisposableClassFixture.cs:line 28


 

Indeed, the fact that I'm calling Dispose on the tested object in Teardown (where expectations were already verified on the mock as this occurs in the test method body), which calls Dispose on the mocked interface is a problem. You can't use a mock in Rhino Mocks after its expectations were verified, even if you're calling a method that does not make part of your expectations. So, I was thinking about how to solve this…

I must call Dispose on the tested object, and I found that Teardown was the right place to do it. I thought I could inject a null reference on _testee.SomeInterface, but this is really not a good thing as it could have side effects on _testee. But maybe Teardown is after all not the right place to Dispose of _testee. So I came with this solution:


 


        [Test]
public void Test()
{
// Expectations setup
_someInterface.DoStuff();
_mocks.ReplayAll();

// Replay
using (_testee)
{
_testee.SomeInterface = _someInterface;
_testee.DoSomeOtherStuff();
}
_mocks.VerifyAll();
}

But frankly, I don't like that. Dispose is really part of cleanup and so should logically go to Teardown, and not pollute every test method. By looking on the MockRepository class, I noticed a method that sounds as it could maybe help me: BackToRecordAll. Let's give it a try…


        [TearDown]
public void TearDown()
{
_mocks.BackToRecordAll();
if (_testee != null)
_testee.Dispose();
}

[Test]
public void Test()
{
// Expectations setup
_someInterface.DoStuff();
_mocks.ReplayAll();

// Replay
_testee.SomeInterface = _someInterface;
_testee.DoSomeOtherStuff();
_mocks.VerifyAll();
}


Aaah much better! This time I can again call Dispose on the mocks without any exception being thrown. Of course, going back to record mode in Teardown is not that intuitive, but still I find it better than having to write cleanup code in my test cases.

2 comments:

Anonymous said...

I had the same problem and this worked for me, thanks.

tchèrry said...

Héhé thanks ! Just what I was looking ;-)