Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing COM events?

We have a home-made COM component written in C++. We now want to test its functions and events in a C# Test Project. The function tests are pretty straight-forward. However, the events are never triggered.

MyLib.MyClass m = new MyLib.MyClass();
Assert.IsTrue(m.doStuff()); // Works

// This does not work. OnMyEvent is never called!
m.MyEvent += new MyLib.IMyClassEvents_MyEventHandler(OnMyEvent);
m.triggerEvent();

I've googled this and read about similar issues here on StackOverflow. I've tried all proposed methods but can't get it working!

So far I've tried running my test with an active dispatcher but with no success. I also tried manually pumping messages in the main thread using Dispatcher.PushFrame(). Nothing. My events never trigger. I created a simple WinForms project and verified that my events work in a normal setup. Hence, this issue only applies to Unit Tests.

Q: How do I make a regular C# Unit Test that can successfully trigger active event handlers?

Somebody out there ought to have a working sample! Please help.

like image 915
l33t Avatar asked Dec 14 '11 08:12

l33t


1 Answers

If your COM object is an STA object, you probably need to run a message loop in order to make its events fire.

You can use a small wrapping around the Application and Form object to do that. Here is a small example I wrote in a few minutes.

Note that I did not run or test it, so it may not work, and the cleanup should probably be better. But it may give you a direction for a solution.

Using this approach, the test class would look something like this:

[TestMethod]
public void Test()
{
    MessageLoopTestRunner.Run(

        // the logic of the test that should run on top of a message loop
        runner =>
        {
            var myObject = new ComObject();

            myObject.MyEvent += (source, args) =>
            {
                Assert.AreEqual(5, args.Value);

                // tell the runner we don't need the message loop anymore
                runner.Finish();
            };

            myObject.TriggerEvent(5);
        },

        // timeout to terminate message loop if test doesn't finish
        TimeSpan.FromSeconds(3));
}

And the code for the MessageLoopTestRunner would be something like that:

public interface IMessageLoopTestRunner
{
    void Finish();
}

public class MessageLoopTestRunner : Form, IMessageLoopTestRunner
{
    public static void Run(Action<IMessageLoopTestRunner> test, TimeSpan timeout)
    {
        Application.Run(new MessageLoopTestRunner(test, timeout));
    }

    private readonly Action<IMessageLoopTestRunner> test;
    private readonly Timer timeoutTimer;

    private MessageLoopTestRunner(Action<IMessageLoopTestRunner> test, TimeSpan timeout)
    {
        this.test = test;
        this.timeoutTimer = new Timer
        {
            Interval = (int)timeout.TotalMilliseconds, 
            Enabled = true
        };

        this.timeoutTimer.Tick += delegate { this.Timeout(); };
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        // queue execution of the test on the message queue
        this.BeginInvoke(new MethodInvoker(() => this.test(this)));
    }

    private void Timeout()
    {
        this.Finish();
        throw new Exception("Test timed out.");
    }

    public void Finish()
    {
        this.timeoutTimer.Dispose();
        this.Close();
    }
}

Does that help?

like image 102
Ran Avatar answered Sep 30 '22 08:09

Ran