Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test a class that uses DispatcherTimer?

I've found a couple of Stack Overflow questions along with a couple of blog posts that already touch on this topic, but unfortunately none of them are meeting my needs. I'll just start with some sample code to show what I'd like to accomplish.

using System;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyApp
{
    [TestClass]
    public class MyTests
    {
        private int _value;

        [TestMethod]
        public async Task TimerTest()
        {
            _value = 0;
            var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
            timer.Tick += IncrementValue;
            timer.Start();

            await Task.Delay(15);
            DispatcherUtils.DoEvents();
            Assert.AreNotEqual(0, _value);
        }

        private void IncrementValue(object sender, EventArgs e)
        {
            _value++;
        } 
    }

    internal class DispatcherUtils
    {
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public static void DoEvents()
        {
            var frame = new DispatcherFrame();
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), frame);
            Dispatcher.PushFrame(frame);
        }

        private static object ExitFrame(object frame)
        {
            ((DispatcherFrame)frame).Continue = false;
            return null;
        }
    }
}

This code works fine if, instead of using a DispatcherTimer, I use an ordinary Timer. But DispatcherTimer never fires. What am I missing? What do I need to get it to fire?

like image 675
soapergem Avatar asked Jun 28 '16 18:06

soapergem


1 Answers

It's best if you can avoid DispatcherTimer in your system under test and use an abstraction instead (Rx has a nice one called IScheduler). This kind of abstraction allows you to explicitly control the flow of time in your unit tests, rather than making your tests conditional on CPU timings.

But if you are just interested in unit testing for now, then you'll need to create an STA thread that does message pumping and have a proper Dispatcher installed. All "run this code on the dispatcher" operations just wrap a delegate in a Win32 message, and if you don't have a Win32 message pumping loop in a Dispatcher (before creating the timer), then those messages won't be processed.

The easiest way to do this is to use WpfContext from here:

[TestMethod]
public async Task TimerTest()
{
  await WpfContext.Run(() =>
  {
    _value = 0;
    var timer = new DispatcherTimer {Interval = TimeSpan.FromMilliseconds(10)};
    timer.Tick += IncrementValue;
    timer.Start();

    await Task.Delay(15);
    Assert.AreNotEqual(0, _value);
  });
}

Again, this kind of approach is substandard because it depends on timings. So if your antivirus gets upset and decides to inspect your unit test, it can spuriously fail. An abstraction like IScheduler enables reliable unit tests.

like image 125
Stephen Cleary Avatar answered Sep 20 '22 10:09

Stephen Cleary