How do I unit test a finalizer?
Asked Answered
W

5

21

I have the following class which is a decorator for an IDisposable object (I have omitted the stuff it adds) which itself implements IDisposable using a common pattern:

public class DisposableDecorator : IDisposable
{
    private readonly IDisposable _innerDisposable;

    public DisposableDecorator(IDisposable innerDisposable)
    {
        _innerDisposable = innerDisposable;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    ~DisposableDecorator()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            _innerDisposable.Dispose();
    }
}

I can easily test that innerDisposable is disposed when Dispose() is called:

[Test]
public void Dispose__DisposesInnerDisposable()
{
    var mockInnerDisposable = new Mock<IDisposable>();

    new DisposableDecorator(mockInnerDisposable.Object).Dispose();

    mockInnerDisposable.Verify(x => x.Dispose());
}

But how do I write a test to make sure innerDisposable does not get disposed by the finalizer? I want to write something like this but it fails, presumably because the finalizer hasn't been called by the GC thread:

[Test]
public void Finalizer__DoesNotDisposeInnerDisposable()
{
    var mockInnerDisposable = new Mock<IDisposable>();

    new DisposableDecorator(mockInnerDisposable.Object);
    GC.Collect();

    mockInnerDisposable.Verify(x => x.Dispose(), Times.Never());
}
Weatherboarding answered 24/5, 2010 at 9:7 Comment(1)
Here you can see the use of IDisposable. That worked fine for me.Alliterative
R
16

I might be misunderstanding, but:

GC.WaitForPendingFinalizers();

Might do the trick - http://msdn.microsoft.com/en-us/library/system.gc.waitforpendingfinalizers.aspx

Rebellion answered 24/5, 2010 at 9:11 Comment(1)
An example in a unit test context would have been helpfulFecula
M
7

When writing unit tests, you should always try to test outside visible behavior, not implementation details. One could argue that supressing finalization is indeed outside visible behavior, but on the other hand, there's probably no way you can (nor should you) mock out the garabage collector.

What you try to make sure in your case, is that a "best-practice" or a coding practice is followed. It should be enforced via a tool that is made for this purpose, such as FxCop.

Milano answered 24/5, 2010 at 9:12 Comment(2)
Yup. Also, you'll never get 100% coverage for your unit tests. Eventually you just have to have faith that your code is going to work, and if you're competent, it should.Etude
I actually had a bug because I'd forgotten to check the disposing flag in Dispose() so wanted to add a test before I fix it.Weatherboarding
E
2

I use Appdomain (see sample below). Class TemporaryFile creates temporary file in constructor and delete's it in Dispose or in finalizer ~TemporaryFile().

Unfortunately, GC.WaitForPendingFinalizers(); doesn't help me to test finalizer.

    [Test]
    public void TestTemporaryFile_without_Dispose()
    {
        const string DOMAIN_NAME = "testDomain";
        const string FILENAME_KEY = "fileName";

        string testRoot = Directory.GetCurrentDirectory();

        AppDomainSetup info = new AppDomainSetup
                                  {
                                      ApplicationBase = testRoot
        };
        AppDomain testDomain = AppDomain.CreateDomain(DOMAIN_NAME, null, info);
        testDomain.DoCallBack(delegate
        {
            TemporaryFile temporaryFile = new TemporaryFile();
            Assert.IsTrue(File.Exists(temporaryFile.FileName));
            AppDomain.CurrentDomain.SetData(FILENAME_KEY, temporaryFile.FileName);
        });
        string createdTemporaryFileName = (string)testDomain.GetData(FILENAME_KEY);
        Assert.IsTrue(File.Exists(createdTemporaryFileName));
        AppDomain.Unload(testDomain);

        Assert.IsFalse(File.Exists(createdTemporaryFileName));
    }
Elliottellipse answered 9/6, 2013 at 20:52 Comment(4)
I don't think there is any way to properly test a finalizer, given that there are a boundless number of threading scenarios under which a finalizer may run. Finalizers may end up running on partially-constructed objects, and should thus generally only be used on classes that are simple enough to allow all scenarios to be validated via inspection. If a class which uses unmanaged resources is too complicated to allow easy inspection, the resources should be encapsulated in their own smaller class, so that the class which holds a reference to the object holding the resources won't need finalizer.Dermatoid
So close! This is really quite clever. It does indeed force the finalizer to run, and gets me 90% to where I wanted to be. However in my case, I need to be able to use a Fakes Shim as well, and the code running in the AppDomain doesn't see the Shim. I can't create the shim inside the DoCallback either because it will be out of scope before the finalizer runs. Has anyone figured that one out?Altair
@SteveInCO could you publish question with sources of your case? Interesting to see sample and search solution.Elliottellipse
@constructor: I ended up just doing without the Assertion that I wanted. My primary goal was to get the code coverage. (I always shoot for 100% so I can easily spot what is missing at a glance as I write new code) I suppose I could have worked around it using a temp file, but that would have required some test-specific code in my class under test, which seemed "dirty". Maybe if I get the time I'll try to put something together to demonstrate and see if anyone can come up with a better solution.Altair
C
2

It's not easy to test finalization, but it can be easier to test if an object is a subject to garbage collection.

This can be done with a weak references.

In a test, it's important to for the local variables to run out of scope before calling GC.Collect(). The easiest way to make sure is a function scope.

    class Stuff
    {
        ~Stuff()
        {
        }
    }

    WeakReference CreateWithWeakReference<T>(Func<T> factory)
    {
        return new WeakReference(factory());
    }

    [Test]
    public void TestEverythingOutOfScopeIsReleased()
    {
        var tracked = new List<WeakReference>();

        var referer = new List<Stuff>();

        tracked.Add(CreateWithWeakReference(() => { var stuff = new Stuff(); referer.Add(stuff); return stuff; }));

        // Run some code that is expected to release the references
        referer.Clear();

        GC.Collect();

        Assert.IsFalse(tracked.Any(o => o.IsAlive), "All objects should have been released");
    }

    [Test]
    public void TestLocalVariableIsStillInScope()
    {
        var tracked = new List<WeakReference>();

        var referer = new List<Stuff>();

        for (var i = 0; i < 10; i++)
        {
            var stuff = new Stuff();
            tracked.Add(CreateWithWeakReference(() => { referer.Add(stuff); return stuff; }));
        }

        // Run some code that is expected to release the references
        referer.Clear();

        GC.Collect();

        // Following holds because of the stuff variable is still on stack!
        Assert.IsTrue(tracked.Count(o => o.IsAlive) == 1, "Should still have a reference to the last one from the for loop");
    }
Courbevoie answered 20/1, 2016 at 15:57 Comment(0)
P
0

I found a way to do it in my Unit Test project using Reflection:

var myObj = new MyType();
var finalizer = typeof(MyType).GetMethod("Finalize", BindingFlags.Instance | BindingFlags.NonPublic);
finalizer.Invoke(myObj, null);
Pestilent answered 19/5 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.