Using the WPF Dispatcher in unit tests

前端 未结 16 2079
北恋
北恋 2020-11-27 02:48

I\'m having trouble getting the Dispatcher to run a delegate I\'m passing to it when unit testing. Everything works fine when I\'m running the program, but, during a unit te

相关标签:
16条回答
  • 2020-11-27 03:26

    You can unit test using a dispatcher, you just need to use the DispatcherFrame. Here is an example of one of my unit tests that uses the DispatcherFrame to force the dispatcher queue to execute.

    [TestMethod]
    public void DomainCollection_AddDomainObjectFromWorkerThread()
    {
     Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
     DispatcherFrame frame = new DispatcherFrame();
     IDomainCollectionMetaData domainCollectionMetaData = this.GenerateIDomainCollectionMetaData();
     IDomainObject parentDomainObject = MockRepository.GenerateMock<IDomainObject>();
     DomainCollection sut = new DomainCollection(dispatcher, domainCollectionMetaData, parentDomainObject);
    
     IDomainObject domainObject = MockRepository.GenerateMock<IDomainObject>();
    
     sut.SetAsLoaded();
     bool raisedCollectionChanged = false;
     sut.ObservableCollection.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs e)
     {
      raisedCollectionChanged = true;
      Assert.IsTrue(e.Action == NotifyCollectionChangedAction.Add, "The action was not add.");
      Assert.IsTrue(e.NewStartingIndex == 0, "NewStartingIndex was not 0.");
      Assert.IsTrue(e.NewItems[0] == domainObject, "NewItems not include added domain object.");
      Assert.IsTrue(e.OldItems == null, "OldItems was not null.");
      Assert.IsTrue(e.OldStartingIndex == -1, "OldStartingIndex was not -1.");
      frame.Continue = false;
     };
    
     WorkerDelegate worker = new WorkerDelegate(delegate(DomainCollection domainCollection)
      {
       domainCollection.Add(domainObject);
      });
     IAsyncResult ar = worker.BeginInvoke(sut, null, null);
     worker.EndInvoke(ar);
     Dispatcher.PushFrame(frame);
     Assert.IsTrue(raisedCollectionChanged, "CollectionChanged event not raised.");
    }
    

    I found out about it here.

    0 讨论(0)
  • 2020-11-27 03:26

    If your goal is to avoid errors when accessing DependencyObjects, I suggest that, rather than playing with threads and Dispatcher explicitly, you simply make sure that your tests run in a (single) STAThread thread.

    This may or may not suit your needs, for me at least it has always been enough for testing anything DependencyObject/WPF-related.

    If you wish to try this, I can point you to several ways to do this :

    • If you use NUnit >= 2.5.0, there is a [RequiresSTA] attribute that can target test methods or classes. Beware though if you use an integrated test runner, as for example the R#4.5 NUnit runner seems to be based on an older version of NUnit and cannot use this attribute.
    • With older NUnit versions, you can set NUnit to use a [STAThread] thread with a config file, see for example this blog post by Chris Headgate.
    • Finally, the same blog post has a fallback method (which I've successfully used in the past) for creating your own [STAThread] thread to run your test on.
    0 讨论(0)
  • 2020-11-27 03:26

    I'm using MSTest and Windows Forms technology with MVVM paradigm. After trying many solutions finally this (found on Vincent Grondin blog) works for me:

        internal Thread CreateDispatcher()
        {
            var dispatcherReadyEvent = new ManualResetEvent(false);
    
            var dispatcherThread = new Thread(() =>
            {
                // This is here just to force the dispatcher 
                // infrastructure to be setup on this thread
                Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { }));
    
                // Run the dispatcher so it starts processing the message 
                // loop dispatcher
                dispatcherReadyEvent.Set();
                Dispatcher.Run();
            });
    
            dispatcherThread.SetApartmentState(ApartmentState.STA);
            dispatcherThread.IsBackground = true;
            dispatcherThread.Start();
    
            dispatcherReadyEvent.WaitOne();
            SynchronizationContext
               .SetSynchronizationContext(new DispatcherSynchronizationContext());
            return dispatcherThread;
        }
    

    And use it like:

        [TestMethod]
        public void Foo()
        {
            Dispatcher
               .FromThread(CreateDispatcher())
                       .Invoke(DispatcherPriority.Background, new DispatcherDelegate(() =>
            {
                _barViewModel.Command.Executed += (sender, args) => _done.Set();
                _barViewModel.Command.DoExecute();
            }));
    
            Assert.IsTrue(_done.WaitOne(WAIT_TIME));
        }
    
    0 讨论(0)
  • 2020-11-27 03:30

    I solved this problem by creating a new Application in my unit test setup.

    Then any class under test which access to Application.Current.Dispatcher will find a dispatcher.

    Because only one Application is allowed in an AppDomain I used the AssemblyInitialize and put it into its own class ApplicationInitializer.

    [TestClass]
    public class ApplicationInitializer
    {
        [AssemblyInitialize]
        public static void AssemblyInitialize(TestContext context)
        {
            var waitForApplicationRun = new TaskCompletionSource<bool>()
            Task.Run(() =>
            {
                var application = new Application();
                application.Startup += (s, e) => { waitForApplicationRun.SetResult(true); };
                application.Run();
            });
            waitForApplicationRun.Task.Wait();        
        }
        [AssemblyCleanup]
        public static void AssemblyCleanup()
        {
            Application.Current.Dispatcher.Invoke(Application.Current.Shutdown);
        }
    }
    [TestClass]
    public class MyTestClass
    {
        [TestMethod]
        public void MyTestMethod()
        {
            // implementation can access Application.Current.Dispatcher
        }
    }
    
    0 讨论(0)
提交回复
热议问题