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.
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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With