|
ms
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Unit testing event handlersHow do you unit test an event handler with NUnit? An event handler is not a test method, and Asserts in test class event handlers will not be tested. So, if the test class is to contain event handlers, the most obvious solution is to return values in the event args and test those results in the test method. But that approach couples the event to the test framework. A better alternative is to use an anonymous delegate: [Test] public void TestOrderDispatchC() { // Create order Order testOrder = new Order(); OrderItem testItem1 = testOrder.AddOrderItem(); testItem1.Status = ItemStatus.Undelivered; OrderItem testItem2 = testOrder.AddOrderItem(); testItem2.Status = ItemStatus.Undelivered; // Create anonymous delegate DeliveryNeededEventHandler anonymousDelegate = delegate(object sender, DeliveryNeededEventArgs e) { Assert.AreEqual(2, e.OrderItems.Count); }; // Subscribe to DeliveryNeeded event testOrder.DeliveryNeeded += anonymousDelegate; // Dispatch order testOrder.DispatchOrder(); // Unsubscribe from the DeliveryNeeded event testOrder.DeliveryNeeded -= anonymousDelegate; } Be sure to unsubscribe from the event at the end of the method. Failing to unsubscribe causes a memory leak (yes, you can get them in .NET). The memory leak occurs with any delegate, not just anonymous delegates, if you don't unsubscribe. David Veeneman Foresight Systems > This post is for the benefit of the Google spider I'm sure it is grateful...> Be sure to unsubscribe from the event at the end of the method. Failing to Once testOrder goes out of scope it is eligible for collection, as is> unsubscribe causes a memory leak (yes, you can get them in .NET). The memory > leak occurs with any delegate, not just anonymous delegates, if you don't > unsubscribe. the delegate assuming you haven't shared it anywhere. If you *had* to unsubscribe you should be using "finally" anyway... Marc Nope--the garbage collector won't collect it if you haven't unsubscribed.
Ask the Princeton team for the DARPA Grand Challenge: http://www.codeproject.com/showcase/IfOnlyWedUsedANTSProfiler.asp A memory leak of this exact type torpedoed their entry in DARPA's $2 million Grand Challenge. So, yes, you do have to unsubscribe. But the suggestion about 'finally' is a good one. David Veeneman Foresight Systems On Nov 27, 3:36 pm, "David Veeneman" <dav...@nospam.com> wrote: Yes, it will.> Nope--the garbage collector won't collect it if you haven't unsubscribed. > Ask the Princeton team for the DARPA Grand Challenge: Yes, you can get garbage collection issues if you don't unsubscribe> > http://www.codeproject.com/showcase/IfOnlyWedUsedANTSProfiler.asp from events - but not the way you're suggesting. > A memory leak of this exact type torpedoed their entry in DARPA's $2 million No, you don't.> Grand Challenge. So, yes, you do have to unsubscribe. You need to understand which way the references work. A delegate instance holds a reference to its target - the object it will call the method on. Suppose you have a form, and some random expensive object subscribes to the Click event of a button on that form: the random expensive object won't be eligible for garbage collection until either the form itself is eligible for garbage collection, or the event handler is unsubscribed - the form effectively has a reference to the object. The reverse is *not* true, however: the form can become eligible for garbage collection *without* the random expensive object becoming eligible. So to cut it short: no, in the unit test example you gave, you do *not* need to unsubscribe. Jon Different problem; they had a long-lived object (the car) and lots of
short-lived objects (the obstacles). The obstacles subscribed and forgot to unsubscribe. However! If the car had been collected, so would have *all* the obstacles. In your scenario you have no long- lived object. Big difference. But yes, if short-lived objects don't unsubscribe, a long-lived object can keep them alive. Marc On 27 Nov, 15:36, "David Veeneman" <dav...@nospam.com> wrote: The following code would suggest you're wrong. Two buttons, the first> Nope--the garbage collector won't collect it if you haven't unsubscribed. > Ask the Princeton team for the DARPA Grand Challenge: > > http://www.codeproject.com/showcase/IfOnlyWedUsedANTSProfiler.asp > > A memory leak of this exact type torpedoed their entry in DARPA's $2 million > Grand Challenge. So, yes, you do have to unsubscribe. > > But the suggestion about 'finally' is a good one. > > David Veeneman > Foresight Systems creates two objects and subscribes to their events. The first object goes out of scope, the second object is assigned to a variable. The second button just does a GC.Collect(), which encourages collection and triggers the finalizers. So you should see in the console: Added Added Destroyed Of course this assumes I understand the problem correctly which is sometimes hit and miss when dealing with this topic. using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace WindowsApplication31 { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button button1; private System.Windows.Forms.Button button2; private System.ComponentModel.Container components = null; private Test _test; public Form1() { InitializeComponent(); } protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button(); this.SuspendLayout(); this.button1.Location = new System.Drawing.Point(16, 8); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(48, 40); this.button1.TabIndex = 0; this.button1.Text = "button1"; this.button1.Click += new System.EventHandler(this.button1_Click); this.button2.Location = new System.Drawing.Point(168, 16); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(40, 40); this.button2.TabIndex = 1; this.button2.Text = "button2"; this.button2.Click += new System.EventHandler(this.button2_Click); this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(292, 273); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); } #endregion [STAThread] static void Main() { Application.Run(new Form1()); } private void button1_Click(object sender, System.EventArgs e) { TestDisconnect d = new TestDisconnect(); d.DoTest(); TestDisconnect d2 = new TestDisconnect(); _test = d2.DoTest(); } private void button2_Click(object sender, System.EventArgs e) { GC.Collect(); } } public class TestDisconnect { public Test DoTest() { Test t = new Test(); t.TestIt+=new TestEventHandler(t_TestIt); return t; } private void t_TestIt(object sender, EventArgs e) { Console.WriteLine("Fired"); } } public delegate void TestEventHandler(object sender, EventArgs e); public class Test { private event TestEventHandler _testIt; public event TestEventHandler TestIt { add { Console.WriteLine("Added"); _testIt+=value; } remove { Console.WriteLine("removed"); _testIt-=value; } } ~Test() { Console.WriteLine("Destroyed"); } } } |
|||||||||||||||||||||||