COM exceptions on exit with WPF
Asked Answered
C

2

13

After execution both of the following test cases, a COM execution is printed to the console. What am I doing wrong?

If I run either test singly, or if I run both tests together, the exception is written to the console exactly once. This makes me suspect that there's some sort of a per-AppDomain resource that I'm not cleaning up.

I have tried the tests with NUnit and with MSTest, with the same behavior in both environments. (Actually, I'm not sure if running both tests in MSTest results in a single exception printout or two.)

Exception:

System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used.
at System.Windows.Input.TextServicesContext.StopTransitoryExtension()
at System.Windows.Input.TextServicesContext.Uninitialize(Boolean appDomainShutdown)
at System.Windows.Input.TextServicesContext.TextServicesContextShutDownListener.OnShutDown(Object target)
at MS.Internal.ShutDownListener.HandleShutDown(Object sender, EventArgs e)

Test code:

using NUnit.Framework;

namespace TaskdockSidebarTests.Client
{
    [TestFixture, RequiresSTA]
    public class ElementHostRCWError
    {
        [Test]
        public void WinForms()
        {
            var form = new System.Windows.Forms.Form();
            var elementHost = new System.Windows.Forms.Integration.ElementHost();
            form.Controls.Add(elementHost);

            // If the form is not shown, the exception is not printed.
            form.Show();

            // These lines are optional. The exception is printed with or without
            form.Close();
            form.Controls.Remove(elementHost);
            elementHost.Dispose();
            form.Dispose();
        }

        [Test]
        public void WPF()
        {
            var window = new Window();

            // If the window is not shown, the exception is not printed.
            window.Show();

            window.Close();
        }
    }
}
Combination answered 3/6, 2011 at 21:28 Comment(4)
Maybe social.msdn.microsoft.com/forums/en-US/vststest/thread/… helpsDiscretional
Sadly, I can't use MTA, as WPF requires STA. Creating the form and element host in SetUp doesn't seem to do the trick, either. Argh.Combination
If i'm not mistaken, this exception does not cause the unittest to fail, does it? I've encountered the same exception while unittesting my WPF controls, i chose to ignore it.. ;)Quadrivalent
It doesn't cause a failure, but when you've got dozens of UI integration tests, it turns into quite a lot of log output.Combination
Q
20

Looking at my own code again, the following line might help for the WPF test, right at the end.

Dispatcher.CurrentDispatcher.InvokeShutdown();
Quadrivalent answered 4/6, 2011 at 11:25 Comment(5)
Sweeeeet! That did it. Thanks! Now I just need to work out how to fit this into my test architecture.Combination
Disappointingly, once a dispatcher is bound to a thread (i.e., Dispatcher.CurrentDispatcher), no other dispatcher can be associated with that thread. And once a dispatcher has been shut down, it cannot be restarted. So while this solves my problem, I sadly can't just put a call to InvokeShutdown() in my base test class's TearDown method.Combination
Try starting a new STA thread in each unittest, perform the test in that new thread, and wait for that thread to finish with Thread.Join().Quadrivalent
I'm trying to avoid being that invasive. Turns out [RequiresThread] does exactly that, though. I've got things working with [RequiresThread] on the class (at the fixture level) and a call to InvokeShutdown() in [TestFixtureTearDown].Combination
Thank you VERY much for that one liner. It saved me a lot of time. I've already spent at least 3hrs on debugging where that exception is coming from and why. It allowed me to finally extend xUnit+Unity teardowns to dispose the AppDomain properly! Sadly, it still really seems like a missing try-catch somewhere in PresentationFramework codeHoloblastic
N
1

You probably can't unit test the Window and Form classes at all. Both WinForms applications and WPF applications have an Application class used to start the underlying plumbing (message pumps and whatnot). I bet it's the key to avoiding that exception.

You're not doing that there and may not be able to.

Every recommendation for unit testing I've ever read is that you refactor so that the Form classes and Window classes don't do anything you need to unit test (like the M-V-VM pattern in WPF). Could have something to do with not being able to show the UI.

There are other ways to test a UI. This answer discusses unit testing UI.

Northnortheast answered 4/6, 2011 at 1:58 Comment(2)
Actually, the tests run fine -- I just end up with a lot of crap in my log files. Regarding UI testing vs. straight business logic testing -- I'm of the belief that the closer I get to testing the actual thing the user deals with, the better I'll sleep at night.Combination
+1 for both Joel and Patrick, because I think you are both right. While I agree with Joel that the design should be like that - you cannot argue that sometimes you just have to automate a few controls/windows just because some old/fragile/notyours code needs it.Holoblastic

© 2022 - 2024 — McMap. All rights reserved.