2

Ok, it seemed like a good idea to make myself a MailService class as a singleton to work with the Outlook class (which itself is a singleton afaik).

It worked great at first, but as soon as I tried setting up unit tests (MS Unit Testing) for this class from my appservice layer all hell broke loose and I get Runtime Callable Wrapper errors..

Was I wrong setting up a MailService class as a singleton or am I doing something wrong in wrapping Outlook as singleton class?

This is my Mailservice class with one of the methods shown:

public class MailService : IDisposable
{

    private static MailService _instance;
    private Outlook.ApplicationClass _app;
    private Outlook.NameSpace _olNS;

    private MailService()
    {
        _app = new Outlook.ApplicationClass();
        _olNS = _app.GetNamespace("MAPI");
    }

    public static MailService Instance
    {
        get
        {
            if (_instance == null)
                _instance = new MailService();
            return _instance;
        }
    }

    public void Dispose()
    {
        _app.Quit();
        Marshal.ReleaseComObject(_app);
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    public Outlook.Folder getFolderByPath(string sFolderPath)
    {
        Outlook.Folder olFolder = null;
        if (sFolderPath.StartsWith(@"\\"))
        {
            sFolderPath = sFolderPath.Remove(0, 2);
        }
        string[] sFolders = sFolderPath.Split('\\');
        try
        {
            olFolder = _app.Session.Folders[sFolders[0]] as Outlook.Folder;
            if (olFolder != null)
            {
                for (int i = 1; i <= sFolders.GetUpperBound(0); i++)
                {
                    Outlook.Folders subFolders = olFolder.Folders;
                    olFolder = subFolders[sFolders[i]] as Outlook.Folder;
                    if (olFolder == null)
                    {
                        return null;
                    }
                }
            }
            return olFolder;
        }
        catch (Exception e)
        {
            LogHelper.MyLogger.Error("Error retrieving " + sFolderPath, e);
            return null;
        }
    }
}

My MS Unit tests are working separately but not when running all in a test list. In the latter condition only the first Test passed...

    [TestMethod]
    public void TestMonitorForCleanUpDone()
    {
        Assert.IsNotNull(MailService.Instance.getFolderByPath(olFolderDone));
    }

    [TestMethod]
    public void TestMonitorForCleanUpIn()
    {
        Assert.IsNotNull(MailService.Instance.getFolderByPath(olFolderIn));
    }
AardVark71
  • 3,928
  • 2
  • 30
  • 50

2 Answers2

1

For starters, your implementation of IDisposable is no good.

 public void Dispose()
 {
    _app.Quit();
    Marshal.ReleaseComObject(_app);
    GC.Collect();
    GC.WaitForPendingFinalizers();
 }

You are goind several things wrong there..

  • you should not force a garbage collection/wait for finalizers
  • you should not dispose of managed resources "blindly"

And since you're making the MailService class a singleton your life-cycle management is problematic because of how you've implemented Dispose().

You should read about properly implementing the IDisposable interface in .NET and also on MSDN Magazine here. And also think about what happens when your MailClass object goes out of scope. And how that is managed in the context of the unit tests.

Mike Dinescu
  • 54,171
  • 16
  • 118
  • 151
  • Thanks for the quick feedback. It does not seem to be directly related to the IDisposable implementation as when I remove it the second unit test still fails. I'm using this garbage collection also for clean up of my excel interop objects.. (not in an Dispose function though but in the finally block) – AardVark71 Jan 23 '13 at 17:15
  • Thanks for pointing me in the direction of the Dispose method and the testcontext of Unit tests! It turned out to be an issue with the Visual Studio UnitTesting framework and can be solved by setting the apartmentState="MTA" on the TestRunConfiguration. – AardVark71 Jan 24 '13 at 10:11
1

As usual it turns out someone else has faced this problem before and wrote a blog post about it: http://blogs.msdn.com/b/martijnh/archive/2009/12/31/unit-testing-com-object-that-has-been-separated-from-its-underlying-rcw-cannot-be-used.aspx

Just in case the blogpost ever goes away, the resolution in short is to run Unit Tests in MTA mode.

"the way to solve this issue is to open up your testrunconfig file using the XML editor and adding the <ExecutionThread apartmentState="MTA" /> element to it"

<?xml version="1.0" encoding="utf-8"?>
<TestRunConfiguration name="Local Test Run" id="f3322344-5454-4ac5-82b7-fa5ba9c1d8f2"     xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
  <ExecutionThread apartmentState="MTA" />
  <Description>This is a default test run configuration for a local test run.</Description>
  <TestTypeSpecific />
</TestRunConfiguration>
AardVark71
  • 3,928
  • 2
  • 30
  • 50
  • Great find! But do keep in mind what I pointed out about your IDisposable implementation.. – Mike Dinescu Jan 24 '13 at 13:47
  • @MikyDinescu Will do so. I'm reading up on blogposts "Marshal.ReleaseComObject Considered Dangerous", "What's so wrong about using GC.Collect()?" etcetera and implemented the suggested Dispose pattern for the part that still matters (_app.Quit()...) – AardVark71 Jan 24 '13 at 14:48