Growing IDisposable Guided By Tests

Test 5: Provide a protected virtual Dispose(bool disposing) method

Use reflection to confirm that there is a Dispose method which takes a boolean parameter, that method is protected and virtual. I would have liked to have used methodInfo.Should().NotBeNull() for the first assertion, but a bug in FluentAssertions prevents NotBeNull() from working correctly if the subject of Should() is a MethodInfo:

[Fact]
public void ProvidesAVirtualProtectedDisposeMethodWithABooleanParameter()
{
    var methodInfo = typeof (ResourceOwner)
                     .GetMethod("Dispose",
                     BindingFlags.Instance 
                         | BindingFlags.NonPublic 
                         | BindingFlags.Public,
                     null,
                     new Type[] {typeof (bool)},
                     null);
 
    Assert.NotNull(methodInfo); //There is a Dispose(bool) method
    methodInfo.IsFamily.Should().BeTrue("the Dispose(bool) method is protected");
    methodInfo.IsVirtual.Should().BeTrue("the Dispose(bool) method is virtual");
}

The test fails. To make it pass we provide a method with the expected signature:

public class ResourceOwner : IDisposable
{
    ...
    protected virtual void Dispose(bool disposing){}
}

The test passes.

 

Test 6: The Dispose() method must call Dispose(true)
[Fact]
public void CallsDisposeWithTrueFromDispose()
{
    var spy = new ResourceSpy();
    spy.Dispose();
    spy.DisposeWasCalled(true).Should().BeTrue();
}

To make it compile:

public class ResourceSpy : ResourceOwner
{
    private bool _disposeWasCalledWithTrue;
    private bool _disposeWasCalledWithFalse;
    ...
    protected override void Dispose(bool disposing)
    {
        _disposeWasCalledWithTrue = disposing;
        _disposeWasCalledWithFalse = !disposing;
        base.Dispose(disposing);
    }
 
    public bool DisposeWasCalled(bool disposing)
    {
        return expected ? _disposeWasCalledWithTrue : _disposeWasCalledWithFalse;
    }
}

The test fails. To make it pass:

public class ResourceOwner : IDisposable
{
    ...
    public void Dispose()
    {
        Dispose(true);
        Marshal.FreeCoTaskMem(AnUnmanagedResource);
        GC.SuppressFinalize(this);
    }
    ...
}

The test passes.

 

Test 7: The finalizer must call Dispose(false)
[Fact]
public void CallsDisposeWithFalseFromFinalizer()
{
    var spy = new ResourceSpy();
    spy.MimicFinalizer();
    spy.DisposeWasCalled(false).Should().BeTrue();
    GC.SuppressFinalize(spy);
}

The test fails. To make it pass:

public class ResourceSpy : ResourceOwner
{
    ...
    public void MimicFinalizer()
    {
        Dispose(false);
        Marshal.FreeCoTaskMem(AnUnmanagedResource);
    }
}

and synchronize:

public class ResourceOwner : IDisposable
{
    ...
    ~ResourceOwner()
    {
        Dispose(false);
        Marshal.FreeCoTaskMem(AnUnmanagedResource);
    }
}

The test passes.

2 thoughts on “Growing IDisposable Guided By Tests

  1. Regarding test 3, you could check your call to GC.SuppressFinalize(this) by introducing a non-public instance method SuppressFinalizeOnThis() which delegates to GC. This then acts like a collaborator that, in some languages, you might pull out into a mixin/trait.

    I don’t think I’d suggest it until you had reason to believe that you weren’t calling GC.SuppressFinalize() correctly.

    1. Hi J.B. Thanks for the reply.

      I thought about doing something like that but it just seems to shift the problem (if I am understanding you correctly). While this would allow me to test that SuppressFinalizeOnThis() was called, the thing I really need to test is that GC.SuppressFinalize(this) was called. I would still need to confirm that this call was being made inside SuppressFinalizeOnThis(). I don’t know how to do that except by visual inspection, which gets me back to the original problem.

Comments are closed.