How to Test a Class Which Uses DispatcherTimer





5.00/5 (19 votes)
Demonstrates how to create unit tests for a class which uses a DispatcherTimer.
Introduction
This article examines how to write unit tests for a class which uses DispatcherTimer
to implement its functionality. We will review what problems exist around testing code which depends on DispatcherTimer
, and how to overcome those obstacles. The technique used to implement the unit tests has been consolidated into a base class named TestWithActiveDispatcher
, which makes it easy to create your own unit tests using this functionality.
The demo projects
This article is accompanied by a Visual Studio solution which contains three projects. One of the projects, ClassLib, is a DLL which contains the classes that are tested by unit tests. The ConsoleTestApp project is a console app which does not use any unit testing framework. The NUnitTests project is a DLL which contains a unit test built on top of the NUnit 2.4 framework. I included the console-based application for people who do not happen to have NUnit 2.4 installed on their machine. The unit test code is essentially the same between both versions.
If you do not have NUnit installed on your machine, open Solution Explorer in Visual Studio, right click on the NUnitTest project, and select 'Unload Project'. That will prevent you from receiving compiler errors related to that project.
Background
The threading model in the Windows Presentation Foundation (WPF) is based on the concept of a "dispatcher". Every thread has a Dispatcher
object associated with it. Dispatcher
is responsible for policing cross-thread operations and provides a way for threads to marshal method invocations to each other, via its BeginInvoke
and Invoke
methods.
Dispatcher
contains the WPF version of a Windows message queue and message pump. It provides a prioritized message queue, from which the message pump can pull messages and process them. Most WPF classes have an affinity for the thread on which they are instantiated. That thread affinity is based on an association between the object and the thread's Dispatcher
.
WPF has a specialized timer class which is aware of dispatchers, named DispatcherTimer
. When a DispatcherTimer
ticks, it enqueues a message in the message queue of the Dispatcher
with which it is associated. By default a DispatcherTimer
will enqueue its messages with the Dispatcher
of the thread on which it was created. When that Dispatcher
's message pump gets around to it, the timer's "tick message" is dequeued and the event handling method for that timer's Tick
event is invoked. For all of this to work properly the Dispatcher
associated with the DispatcherTimer
must be running; its message pump must be "pumping".
The problem
DispatcherTimer
works like a charm when using it in a WPF application. You usually would never need to know the underlying details of how it interacts with a Dispatcher
. However, once you start using DispatcherTimer
outside of its natural environment (i.e. a WPF application) it suddenly becomes a rather complicated class to work with.
One reasonable scenario in which a DispatcherTimer
might be used outside of a WPF application is if it happens to be running within a unit test. If you have a class which uses DispatcherTimer
to implement its functionality and want to create unit tests for that class, you will find that there are several obstacles to overcome before the unit tests run properly.
Here are several issues related to testing a class whose functionality depends on the use of DispatcherTimer
:
- Threads that run unit tests do not have an active
Dispatcher
by default. - The method which activates a
Dispatcher
is a blocking call, so you cannot execute any code which follows that method call until theDispatcher
is shut down. This makes it difficult to start aDispatcher
and then run the test code which needs an activeDispatcher
. - The default priority given to a
DispatcherTimer
's tick message in a message queue is so low that the tick messages are never processed by the message pump. This is not a problem in a normal WPF application, but the behavior can be observed in console applications and when running the NUnit GUI app. - You need to shut down a thread's
Dispatcher
for a unit test method to complete. Once you shut down a thread'sDispatcher
, you cannot restart it or give the thread a newDispatcher
. Keep in mind that multiple unit test methods might need to run on the same thread. - Since the code being tested involves the use of a timer, your test code must be able to wait idly so that the object being tested has enough time to complete its work. However, since the
DispatcherTimer
being used runs on the same thread as the code which tests it, you cannot put the thread to sleep or put it into a simple "stand by" loop. If you were to use either of those two approaches, theDispatcher
's message pump would not be given a chance to process the timer's tick messages (because the thread it runs on would either be sleeping or stuck executing a "stand by" loop).
The solution
As you might imagine, the solution to this problem requires some fancy footwork. I created the TestWithActiveDispatcher
base class to encapsulate the heavy lifting, so that we can simply subclass it and easily make as many unit tests involving DispatcherTimer
s as we need. This section of the article reviews the high-level concepts in TestWithActiveDispatcher
. The 'How it works' section later on shows how the class was implemented.
I think the clearest way to explain how TestWithActiveDispatcher
works is with images and brief blurbs about the images. The following five steps show how it works.
Step 1 – Spawn a worker thread to run the test method
First a worker thread is created and the main thread joins to it, so that the main thread will not execute until the worker thread dies.
Step 2 – Worker thread posts a delayed call to the test method
When the worker thread is up and running, it enqueues a message to its inactive Dispatcher
's message queue. The message is a request to execute the method which contains the test code. At this point the worker thread's Dispatcher
is not running yet, so the message just sits in the queue and waits for the Dispatcher
to eventually process it.
Step 3 – Worker thread starts its Dispatcher
At this point the worker thread tells its Dispatcher
to start running. This activates the message pump, which then processes the message waiting in its queue (the message posted there in the previous step). When the message is processed it causes the test method to be executed. The test method now runs on a thread which has an active Dispatcher
, so it can properly exercise the code which uses a DispatcherTimer
.
Step 4 – DispatcherTimer makes use of the active Dispatcher
Now the code under test is being exercised by the test method, and it creates a DispatcherTimer
which places tick messages into the Dispatcher
's message queue. The Dispatcher
's message pump processes the tick messages and invokes the method which handles the Tick
event. As far as the code being tested is concerned, it may as well be executing in a normal WPF application. It has an active Dispatcher
processing its tick messages and all is well.
Step 5 – Test method completes and the Dispatcher is shut down
Once the test method determines that the test is over, the Dispatcher
is shut down so that the worker thread can die.
Putting TestWithActiveDispatcher to use
In this section we see how to write a class which descends from TestWithActiveDispatcher
, and tests a simple class which uses a DispatcherTimer
. The class under test is called Ticker
. Ticker
is a class which prints "Tick!" to the console window a specified number of times, at a specified time interval. For example, it might be configured to print "Tick!" three times, once every two seconds.
The Ticker
class exposes a public API which looks like this:
interface ITicker
{
void Start();
int Ticks { get; }
}
The Start
method tells the Ticker
to begin printing "Tick!" to the console. The Ticks
property returns the number of times the Ticker
has ticked since the last call to Start
.
Ticker
's constructor requires you to pass in a TickerSettings
object. That class is used to configure the Ticker
instance with various settings. Here is the public API for TickerSettings
:
interface ITickerSettings
{
TimeSpan Interval { get; }
int NumberOfTicks { get; }
DispatcherPriority Priority { get; }
}
The test we want to create will verify that Ticker
honors the Interval
and NumberOfTicks
settings. Here are the setup and test methods, based on the NUnit 2.4 framework:
/// <summary>
/// Initializes data used in the test.
/// </summary>
[SetUp]
public void ConfigureTickerSettings()
{
TimeSpan interval = TimeSpan.FromSeconds(1);
int numberOfTicks = 3;
DispatcherPriority priority = DispatcherPriority.Normal;
// VERY IMPORTANT!!!
// For the DispatcherTimer to tick when it is not running
// in a normal WPF application, you must give it a priority
// higher than 'Background' (which is the default priority).
// In this demo we give it a priority of 'Normal'.
_settings = new TickerSettings(interval, numberOfTicks, priority);
}
[Test]
public void TickerHonorsIntervalAndNumberOfTicks()
{
_ticker = null;
base.BeginExecuteTest();
Assert.IsNotNull(_ticker, "_ticker should have been assigned a value.");
Assert.AreEqual(3, _ticker.Ticks);
}
The comment in the setup method points out a very important fact. If you are going to run a DispatcherTimer
in a unit test, you need to be sure to give it a priority higher than the default value of 'Background
'. If you do not do this, the timer tick messages will never be processed. I don't know why this is true, but it's true.
The test method seems rather empty. However, keep in mind that the class our test lives in derives from the TestWithActiveDispatcher
class. When we call BeginExecuteTest
it will eventually result in an invocation of the following overridden method:
protected override void ExecuteTestAsync()
{
Debug.Assert(base.IsRunningOnWorkerThread);
// Note: The object which creates a DispatcherTimer
// must create it with the Dispatcher for the worker
// thread. Creating the Ticker on the worker thread
// ensures that its DispatcherTimer uses the worker
// thread's Dispatcher.
_ticker = new Ticker(_settings);
// Tell the Ticker to start ticking.
_ticker.Start();
// Give the Ticker some time to do its work.
TimeSpan waitTime = this.CalculateWaitTime();
base.WaitWithoutBlockingDispatcher(waitTime);
// Let the base class know that the test is over
// so that it can turn off the worker thread's
// message pump.
base.EndExecuteTest();
}
This is the method which exercises the Ticker
class. It is important to note that the Ticker
instance being tested is created in this method. Since this method executes on a worker thread spawned by TestWithActiveDispatcher
, we need to be sure to create the Ticker
in this method. Doing so ensures that when Ticker
creates its DispatcherTimer
, the correct (and active) Dispatcher
will be associated with it.
After the Ticker
is told to start ticking, we need to wait around until enough time has elapsed for the Ticker
to get its work done. To achieve that we call the WaitWithoutBlockingDispatcher
method, which is inherited from TestWithActiveDispatcher
, and tell it how long to wait. The logic which determines how long to wait looks like this:
TimeSpan CalculateWaitTime()
{
Debug.Assert(base.IsRunningOnWorkerThread);
// Calculate how much time the Ticker needs to perform
// all of it's ticks. Add some extra time to make sure
// it does not tick more than it should.
int numTicks = _settings.NumberOfTicks + 1;
int tickInterval = (int)_settings.Interval.TotalSeconds;
int secondsToWait = numTicks * tickInterval;
TimeSpan waitTime = TimeSpan.FromSeconds(secondsToWait);
return waitTime;
}
One last thing to note is that at the end of the overridden ExecuteTestAsync
method seen above, the EndExecuteTest
method is called. This is a required step because it lets TestWithActiveDispatcher
know that it should shut down the worker thread's Dispatcher
. Once the Dispatcher
is inactive, the test is over and another test method can be run.
How it works
In this section we will see how the TestWithActiveDispatcher
class works. It is not necessary to read this section in order to use the class. This section breaks the implementation down into the same five steps seen in the previous 'The solution' section.
Step 1 – Spawn a worker thread to run the test method
When the subclass wants to run its test code, it needs to call the BeginExecuteTest
method. That method kicks off the whole process by spawning a thread which executes the BeginExecuteTestAsync
method and then waiting for that thread to die. When the worker thread dies, the test is over.
/// <summary>
/// Calling this method causes the ExecuteTestAsync override to be
/// invoked in the child class. Invoke this method to initiate the
/// asynchronous testing.
/// </summary>
protected void BeginExecuteTest()
{
Debug.Assert(this.IsRunningOnWorkerThread == false);
// Run the test code on an STA worker thread
// and then wait for that thread to die before
// this method continues executing. We use an
// STA thread because many WPF classes require it.
// We must do this work on a worker thread because
// once a thread's Dispatcher is shut down it cannot
// be run again.
Thread thread = new Thread(this.BeginExecuteTestAsync);
thread.SetApartmentState(ApartmentState.STA);
thread.Name = WORKER_THREAD_NAME;
thread.Start();
thread.Join();
}
Steps 2 & 3 – Worker thread posts a delayed call to the test method and starts its Dispatcher
At this point the rest of the code we review executes on a worker thread. The BeginExecuteTestAsync
method is responsible for solving the chicken and egg problem of starting the Dispatcher
and executing the test method which requires an active Dispatcher
(remember, Dispatcher.Run
is a blocking call).
void BeginExecuteTestAsync()
{
Debug.Assert(this.IsRunningOnWorkerThread);
NoArgDelegate testMethod = new NoArgDelegate(this.ExecuteTestAsync);
// The call to ExecuteTestAsync must be delayed so
// that the worker thread's Dispatcher can be started
// before the method executes. This is needed because
// the Dispatcher.Run method does not return until the
// Dispatcher has been shut down.
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Normal, testMethod);
// Start the worker thread's message pump so that
// queued messages are processed.
Dispatcher.Run();
}
When the call to Dispatcher.Run
returns that means the Dispatcher
has been shut down and the test method is complete. At that point the worker thread dies and the BeginExecuteTest
method seen in Step 1 is finished as well.
Step 4 – DispatcherTimer makes use of the active Dispatcher
The child class must override ExecuteTestAsync
and put the test code there. That is the correct place to put code which directly or indirectly creates a DispatcherTimer
. When this method is invoked, the worker thread's Dispatcher
is already running and ready to process messages.
/// <summary>
/// Derived classes must override this method to implement their test logic.
/// Note: this method is invoked on a worker thread with active Dispatcher.
/// </summary>
protected abstract void ExecuteTestAsync();
Step 5 – Test method completes and the Dispatcher is shut down
At the end of the overridden ExecuteTestAsync
the child class is expected to call EndExecuteTest
. That method shuts down the worker thread's Dispatcher
.
/// <summary>
/// Stops the worker thread's message pump. Derived classes
/// should call this method at the end of their ExecuteTestAsync
/// override to shut down the worker thread's Dispatcher.
/// Note: this method must be invoked from the worker thread.
/// </summary>
protected void EndExecuteTest()
{
Debug.Assert(this.IsRunningOnWorkerThread);
Dispatcher disp = Dispatcher.CurrentDispatcher;
Debug.Assert(disp.HasShutdownStarted == false);
// Kill the worker thread's Dispatcher so that the
// message pump is shut down and the thread can die.
disp.BeginInvokeShutdown(DispatcherPriority.Normal);
}
The last step – Waiting idly while the test executes
There is one final detail involved with this class. While the code being tested is running TestWithActiveDispatcher
needs to wait around idly, such that it does not prevent the Dispatcher
's message pump from getting a chance to process messages. This involves the use of an old trusty evil hack…DoEvents
.
In case you are not familiar with DoEvents
, just think of it as a way of processing all of the messages in the message queue in one fell swoop. I used this wretched technique here so that the DispatcherTimer
's tick messages would get a chance to be processed by the message pump. If I did not use it, the while
loop seen below would dominate the worker thread until the loop finished.
WPF does not have its own version of DoEvents
but a fellow by the name of Zhou Yong posted a smart way of implementing it on his blog, and I wrapped that code into a utility class named DispatcherHelper
. (See the 'References' section below for a link to the page I'm referring to).
/// <summary>
/// Pauses the worker thread for the specified duration, but allows
/// the Dispatcher to continue processing messages in its message queue.
/// Note: this method must be invoked from the worker thread.
/// </summary>
/// <param name="waitTime">The amount of time to pause.</param>
protected void WaitWithoutBlockingDispatcher(TimeSpan waitTime)
{
Debug.Assert(this.IsRunningOnWorkerThread);
// Save the time at which the wait began.
DateTime startTime = DateTime.Now;
bool wait = true;
while (wait)
{
// Call DoEvents so that all of the messages in
// the worker thread's Dispatcher message queue
// are processed.
DispatcherHelper.DoEvents();
// Check if the wait is over.
TimeSpan elapsed = DateTime.Now - startTime;
wait = elapsed < waitTime;
}
}
I would not advise using DoEvents
in production code, unless it is absolutely positively necessary. DoEvents
is like applying a severe earthquake to the state of your application. Since this is only test code I'm not too worried about it.
References
- Instantiating a WPF control from an NUnit test
- Application.DoEvents in WPF
- Threading Model
- Dispatcher Class
- DispatcherTimer Class
Revision History
- July 14, 2007 – Created the article