Testing Asynchronous Events

The timing seems right to demonstrate a pattern for testing asynchronous processing. This follows up on Steve's assertion: Don't make assumptions, write the test.

For purposes of demonstration our user story will be: As a program I want to output a message after an event is raised. So let us start by writing a test. Our story implies an event reader and a writer. So let us use the runtime execution provided by test runner and make that the reader. The writer will be some other simple class.

namespace Test.AsynchronousProcessing
{
    [TestFixture]
    public class EventReader
    {
        [Test]
        public void CreateInstance()
        {
            Assert.IsInstanceOfType(typeof (EventWriter), new EventWriter());
        }
    }
}
If you run the tests for EventReader, CreateInstance will fail of course. So let us do the minimal necessary to make the test pass: create EventWriter class. By the way...by convention the code listings show only relevant material.
    public class EventWriter
    {
    }

So now the tests pass and we can move on to the next test: reading and writing an event. So we write the following test; it fails and we make it pass.

    [TestFixture]
    public class EventReader
    {
        // off-topic contents elided

        [Test]
        public void CanSubscribeToEvent()
        {
            EventWriter eventWriter = new EventWriter();
            Assert.AreEqual(0, eventWriter.NumberOfReaders);
            eventWriter.Event += delegate { };
            Assert.AreEqual(1, eventWriter.NumberOfReaders);
        }
    }

    public class EventWriter
    {
        public event EventHandler Event;

        public int NumberOfReaders
        {
            get { return null == Event ? 0 : Event.GetInvocationList().Length; }
        }
    }
We have the beginnings of a pattern for testing asynchronous process. Before we move on, there is something wrong with our test. It leaves the EventReader registered to Event. This is fine for a short-lived AppDomain - but nontheless incomplete. So let us correct CanSubscribeToEvent.
        [Test]
        public void CanSubscribeToEvent()
        {
            EventWriter eventWriter = new EventWriter();
            Assert.AreEqual(0, eventWriter.NumberOfReaders);
            EventHandler reader = delegate { };
            try
            {
                eventWriter.Event += reader;
                Assert.AreEqual(1, eventWriter.NumberOfReaders);
            }
            finally
            {
                eventWriter.Event -= reader;
                Assert.AreEqual(0, eventWriter.NumberOfReaders);
            }
        }
    }

So now we have all tests passed and full coverage of our domain. Time to finish the story. The program (EventReader) wants to output a message when the event is raised. Let us write a new test to assert that behavior. We do so; it fails and we make it succeed.

        // off-topic contents elided

        [Test]
        public void CanWriteMessageWhenEventIsRaised()
        {
            EventWriter eventWriter = new EventWriter();
            Assert.AreEqual(0, eventWriter.NumberOfReaders);
            ManualResetEvent signal = new ManualResetEvent(false);
            EventHandler reader = delegate { signal.Set(); };
            try
            {
                eventWriter.Event += reader;
                Assert.AreEqual(1, eventWriter.NumberOfReaders);
                eventWriter.RaiseEvent(this, null);
                Assert.IsTrue(signal.WaitOne(500, false));
                Console.WriteLine("Event notification received.");
            }
            finally
            {
                eventWriter.Event -= reader;
                Assert.AreEqual(0, eventWriter.NumberOfReaders);
                signal.Close();
            }
        }
    }

    public class EventWriter
    {
        public event EventHandler Event;

        public int NumberOfReaders
        {
            get { return null == Event ? 0 : Event.GetInvocationList().Length; }
        }

        public void RaiseEvent(object sender, EventArgs eventArgs)
        {
            if (Event != null) Event(sender, eventArgs);
        }
    }

I could have jumped ahead to the topic of this article: testing asynchronous processing. But would that have been more or less revealing? My assertion is that tests are the best form of communicating behavior and rules of a system. In fact I will be writing about that in my next article.

I often find developers and managers reluctant to test so-called "untestable" aspects of a system. This usually includes UI controls, but often includes events that are assumed to be tested by using the system. Carl Sagan's The Dragon In My Garage describes this aversion to challenge the "untestable".

Now, what's the difference between an invisible, incorporeal, floating dragon who spits heatless fire and no dragon at all? If there's no way to disprove my contention, no conceivable experiment that would count against it, what does it mean to say that my dragon exists? Your inability to invalidate my hypothesis is not at all the same thing as proving it true. Claims that cannot be tested, assertions immune to disproof are veridically worthless, whatever value they may have in inspiring us or in exciting our sense of wonder. What I'm asking you to do comes down to believing, in the absence of evidence, on my say-so.

Let us close by completing our technical understanding our new pattern for testing asynchronous processing. There are two main players here: a semaphore abstracted by ManualResetEvent and an anonymous delegate. Using these two we can test any asynchronous processing. The ManualResetEvent is a signal. It starts off in the unsignaled state. By setting the signal a thread can then branch its behavior. The thread is free to block, waiting for a signal (as we have done) or to periodically check for the signal.

The second player here is a language construct originally specified in Prolog...but recently implemented in C# 2.0 among other languages: anonymous methods. You may already have been using these. What we have done here is maintain or increase your development velocity by using the anonymous delegate. The alternative would be to define an strong-typed delegate for each type of asynchronous event being tested. In most cases that is unnecessary overhead.

So that is our (currently) recommended approach to testing asynchronous processing in .NET 2.0. As always we encourage you to comment and challenge on this.