I want to test that setting a certain property (or more generally, executing some code) raises a certain event on my object. In that respect my problem is similar to Unit testing that an event is raised in C#, but I need a lot of these tests and I hate boilerplate. So I'm looking for a more general solution, using reflection.
Ideally, I would like to do something like this:
[TestMethod]
public void TestWidth() {
MyClass myObject = new MyClass();
AssertRaisesEvent(() => { myObject.Width = 42; }, myObject, "WidthChanged");
}
For the implementation of the AssertRaisesEvent
, I've come this far:
private void AssertRaisesEvent(Action action, object obj, string eventName)
{
EventInfo eventInfo = obj.GetType().GetEvent(eventName);
int raisedCount = 0;
Action incrementer = () => { ++raisedCount; };
Delegate handler = /* what goes here? */;
eventInfo.AddEventHandler(obj, handler);
action.Invoke();
eventInfo.RemoveEventHandler(obj, handler);
Assert.AreEqual(1, raisedCount);
}
As you can see, my problem lies in creating a Delegate
of the appropriate type for this event. The delegate should do nothing except invoke incrementer
.
Because of all the syntactic syrup in C#, my notion of how delegates and events really work is a bit hazy. This is also the first time I dabble in reflection. What's the missing part?
When testing that a class raises events correctly, there are three elements to verify. Firstly, the class should raise the correct event according to the process being executed. Secondly, the event should refer to the object that raised it.
The most scalable way to write unit tests in C is using a unit testing framework, such as: CppUTest. Unity. Google Test.
A Unit Testing Framework for CCUnit is a lightweight system for writing, administering, and running unit tests in C. It provides C programmers a basic testing functionality with a flexible variety of user interfaces.
A unit test is a way of testing a unit - the smallest piece of code that can be logically isolated in a system. In most programming languages, that is a function, a subroutine, a method or property.
I recently wrote a series of blog posts on unit testing event sequences for objects that publish both synchronous and asynchronous events. The posts describe a unit testing approach and framework, and provides the full source code with tests.
I describe the implementation of an "event monitor" which allows writing event sequencing unit tests to be written more cleanly i.e. getting rid of all the messy boilerplate code.
Using the event monitor described in my article, tests can be written like so:
var publisher = new AsyncEventPublisher();
Action test = () =>
{
publisher.RaiseA();
publisher.RaiseB();
publisher.RaiseC();
};
var expectedSequence = new[] { "EventA", "EventB", "EventC" };
EventMonitor.Assert(publisher, test, expectedSequence);
Or for a type that implements INotifyPropertyChanged:
var publisher = new PropertyChangedEventPublisher();
Action test = () =>
{
publisher.X = 1;
publisher.Y = 2;
};
var expectedSequence = new[] { "X", "Y" };
EventMonitor.Assert(publisher, test, expectedSequence);
And for the case in the original question:
MyClass myObject = new MyClass();
EventMonitor.Assert(myObject, () => { myObject.Width = 42; }, "Width");
The EventMonitor does all the heavy lifting and will run the test (action) and assert that events are raised in the expected sequence (expectedSequence). It also prints out nice diagnostic messages on test failure. Reflection and IL are used under the hood to get the dynamic event subscription working, but this is all nicely encapsulated, so only code like the above is required to write event tests.
There's a lot of detail in the posts describing the issues and approaches, and source code too:
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
With lambdas you can do this with very little code. Just assign a lambda to the event, and set a value in the handler. No need for reflection and you gain strongly typed refactoring
[TestFixture]
public class TestClass
{
[Test]
public void TestEventRaised()
{
// arrange
var called = false;
var test = new ObjectUnderTest();
test.WidthChanged += (sender, args) => called = true;
// act
test.Width = 42;
// assert
Assert.IsTrue(called);
}
private class ObjectUnderTest
{
private int _width;
public event EventHandler WidthChanged;
public int Width
{
get { return _width; }
set
{
_width = value; OnWidthChanged();
}
}
private void OnWidthChanged()
{
var handler = WidthChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
}
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