2009-07-09 28 views
45

単体テスト時に代理人を実行するためにDispatcherを呼び出すのに問題があります。私はプログラムを実行しているとき、すべてが正常に動作しますが、ユニットテスト中に次のコードは実行されません。単体テストでWPFディスパッチャを使用する

this.Dispatcher.BeginInvoke(new ThreadStart(delegate 
{ 
    this.Users.Clear(); 

    foreach (User user in e.Results) 
    { 
     this.Users.Add(user); 
    } 
}), DispatcherPriority.Normal, null); 

を私はディスパッチャを得るために、私のviewmodel基底クラスでこのコードを持っている:

if (Application.Current != null) 
{ 
    this.Dispatcher = Application.Current.Dispatcher; 
} 
else 
{ 
    this.Dispatcher = Dispatcher.CurrentDispatcher; 
} 

ユニットテストのためにディスパッチャを初期化するために必要なことはありますか? Dispatcherはデリゲート内でコードを実行しません。

+0

どのようなエラーが表示されますか? –

+0

エラーは発生しません。 DispatcherのBeginInvokeに渡されたものは決して実行されません。 –

+1

私は正直で、ディスパッチャーをまだ利用しているビューモデルをユニットテストする必要はなかったと言います。ディスパッチャが実行されていない可能性がありますか?あなたのテストのヘルプでDispatcher.CurrentDispatcher.Run()を呼び出すのでしょうか?私は好奇心が強いので、もしあなたがそれらを取得した場合、結果を投稿してください。 –

答えて

82

Visual Studio Unit Test Frameworkを使用することで、Dispatcherを自分で初期化する必要はありません。 Dispatcherがキューを自動的に処理しないことは間違いありません。

単純なヘルパー・メソッド "DispatcherUtil.DoEvents()"を作成すると、Dispatcherにそのキューを処理するように指示できます。

C#コード:

public static class DispatcherUtil 
{ 
    [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] 
    public static void DoEvents() 
    { 
     DispatcherFrame 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; 
    } 
} 

あなたはWPF Application Framework (WAF)にあまりにもこのクラスを見つけます。 DipatcherFrameの作成

+4

このソリューションは、順次作成されたテストケースで実行できますが、受け入れられる回答では、テストコードがコールバック指向のアプローチで記述されている必要があります。 –

+0

ブリリアント。共有してくれてありがとう。 – ozczecho

+3

これは私のために素晴らしく働いた。これは私の受け入れられた答えです –

2

Dispatcher.BeginInvokeを呼び出すと、スレッドがアイドル状態のときにそのスレッドでデリゲートを実行するように指示しています。

ユニットテストを実行すると、メインスレッドは決してになります。すべてのテストを実行して終了します。

このアスペクトユニットをテスト可能にするには、メインスレッドのディスパッチャを使用しないように、基礎となるデザインを変更する必要があります。もう1つの方法は、 System.ComponentModel.BackgroundWorkerを使用して、別のスレッドのユーザーを変更する方法です。 (これは一例に過ぎず、文脈によっては不適切かもしれない)。


編集(5ヶ月後) DispatcherFrameの気付かない間、私はこの答えを書きました。 DispatcherFrameは非常に便利であることが判明しました。

0

あなたの目標はDependencyObject Sにアクセスする際のエラーを回避することであるならば、私はむしろスレッドと明示的Dispatcherで遊んよりも、あなたは、単にあなたのテストは(シングル)STAThreadスレッドで実行されていることを確認します、ことを示唆しています。

これはあなたのニーズに合っているかもしれませんし、少なくとも私は常にDependencyObject/WPF関連のものをテストするのに十分でした。

あなたはこれを試してみたい場合は、私はこれを行うには、いくつかの方法にあなたを指すことができますが:

  • あなたはNUnitの> = 2.5.0を使用する場合は、テストメソッドやクラスをターゲットにすることができ[RequiresSTA]属性があります。ただし、R#4.5のNUnitランナーは古いバージョンのNUnitに基づいているようで、この属性を使用することはできませんので、統合テストランナーを使用する場合は注意してください。
  • 古いNUnitバージョンでは、設定ファイルで[STAThread]スレッドを使用するようにNUnitを設定できます。たとえば、Chris Headgateのthis blog postを参照してください。
  • 最後に、the same blog postには、テストを実行するための独自の[STAThread]スレッドを作成するためのフォールバックメソッド(これまでは正常に使用していました)があります。
15

ディスパッチャを使用してユニットテストを行うことができます。ディスパッチャフレームを使用するだけで済みます。 DispatcherFrameを使用してディスパッチャ・キューを強制実行する私のユニット・テストの1つの例を次に示します。

[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."); 
} 

私はそれhere知りました。

+0

ええ、ちょうど私が最後にどのようにこの質問を更新するために戻ってきました。私は私が考える同じポストを読んだ! –

2

が私のために素晴らしい仕事:

[TestMethod] 
public void Search_for_item_returns_one_result() 
{ 
    var searchService = CreateSearchServiceWithExpectedResults("test", 1); 
    var eventAggregator = new SimpleEventAggregator(); 
    var searchViewModel = new SearchViewModel(searchService, 10, eventAggregator) { SearchText = searchText }; 

    var signal = new AutoResetEvent(false); 
    var frame = new DispatcherFrame(); 

    // set the event to signal the frame 
    eventAggregator.Subscribe(new ProgressCompleteEvent(),() => 
     { 
      signal.Set(); 
      frame.Continue = false; 
     }); 

    searchViewModel.Search(); // dispatcher call happening here 

    Dispatcher.PushFrame(frame); 
    signal.WaitOne(); 

    Assert.AreEqual(1, searchViewModel.TotalFound); 
} 
20

我々は単にインターフェースの後ろにディスパッチャをあざけることにより、この問題を解決し、そして私たちのIOCコンテナからインターフェースに引っ張ってきました。ここインターフェースです:

public interface IDispatcher 
{ 
    void Dispatch(Delegate method, params object[] args); 
} 

は、ここで実際のアプリ

[Export(typeof(IDispatcher))] 
public class ApplicationDispatcher : IDispatcher 
{ 
    public void Dispatch(Delegate method, params object[] args) 
    { UnderlyingDispatcher.BeginInvoke(method, args); } 

    // ----- 

    Dispatcher UnderlyingDispatcher 
    { 
     get 
     { 
      if(App.Current == null) 
       throw new InvalidOperationException("You must call this method from within a running WPF application!"); 

      if(App.Current.Dispatcher == null) 
       throw new InvalidOperationException("You must call this method from within a running WPF application with an active dispatcher!"); 

      return App.Current.Dispatcher; 
     } 
    } 
} 

のためのIOCコンテナに登録された具体的な実装だと、ここで私たちはユニットテスト中にコードに供給モック一つだ:

public class MockDispatcher : IDispatcher 
{ 
    public void Dispatch(Delegate method, params object[] args) 
    { method.DynamicInvoke(args); } 
} 

また、代理人をバックグラウンドスレッドで実行するMockDispatcherの亜種もありますが、ほとんどの場合は必ずしも必要ではありません

+0

DispatcherInvokeメソッドをモックする方法は? – lukaszk

+0

@lukaszk、あなたの模擬フレームワークに応じて、あなたが模擬的にInvokeメソッドをセットアップして、渡されたデリゲートを実際に実行します(必要な振る舞いだった場合)。あなたは必ずしもそのデリゲートを実行する必要はありません、私はちょうど正しいデリゲートがモックに渡されたことを確認するいくつかのテストがあります。 –

2

ロジックをjbe's answer~ディスパッチャ(いずれもDispatcher.CurrentDispatcher以外)に適用する場合は、次の拡張方法を使用できます。

public static class DispatcherExtentions 
{ 
    public static void PumpUntilDry(this Dispatcher dispatcher) 
    { 
     DispatcherFrame frame = new DispatcherFrame(); 
     dispatcher.BeginInvoke(
      new Action(() => frame.Continue = false), 
      DispatcherPriority.Background); 
     Dispatcher.PushFrame(frame); 
    } 
} 

使用:

Dispatcher d = getADispatcher(); 
d.PumpUntilDry(); 

現在ディスパッチャで使用する:

Dispatcher.CurrentDispatcher.PumpUntilDry(); 

それはより多くの状況で使用することができるので、私はこの変化を好むは、以下のコードを使用して実装され、そしてより直感的な構文を持っています。

DispatcherFrameの追加の背景については、excellent blog writeupをご覧ください。

+1

これは奇妙なメソッド名です。 –

0

MVVMのパラダイムでMSTestWindows Forms技術を使用しています。 は、多くのソリューションに私のために最終的には、この(found on Vincent Grondin blog)作品試した後:

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; 
    } 

など、それを使用します。

[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)); 
    } 
1

を私はユニットテストのセットアップで新しいアプリケーションを作成することによって、この問題を解決しました。

次に、Application.Current.Dispatcherにアクセスするテスト対象のクラスでディスパッチャが見つかります。

AppDomainでは1つのアプリケーションしか使用できないため、私はAssemblyInitializeを使用して独自の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

私はDoEventsSync(それを呼び出す)とちょうどBeginInvokeメソッドの代わりに起動するディスパッチャを呼び出しDispatcherUtil 1つの以上の方法を追加することをお勧めします。これは、Dispatcherがすべてのフレームを処理するまで実際に待つ必要がある場合に必要です。

public static class DispatcherUtil 
    { 
     [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); 
     } 

     public static void DoEventsSync() 
     { 
      var frame = new DispatcherFrame(); 
      Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, 
       new DispatcherOperationCallback(ExitFrame), frame); 
      Dispatcher.PushFrame(frame); 
     } 

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

私は自分IDispatcherインターフェイスでDispatcherをラップすることによって、これを達成し、それへの呼び出しを検証するために部品番号をされた使用:クラス全体が長いにあるので、私は、他の回答だけでなく、コメントとしてこれを掲示しています製。

IDispatcherインタフェース:

public interface IDispatcher 
{ 
    void BeginInvoke(Delegate action, params object[] args); 
} 

実ディスパッチャの実装:この場合には(ユニットテスト内部ディスパッチャのモック

public ClassUnderTest(IDispatcher dispatcher = null) 
{ 
    _dispatcher = dispatcher ?? new UiDispatcher(Application.Current?.Dispatcher); 
} 

:テスト対象のクラスでディスパッチャの初期化

class RealDispatcher : IDispatcher 
{ 
    private readonly Dispatcher _dispatcher; 

    public RealDispatcher(Dispatcher dispatcher) 
    { 
     _dispatcher = dispatcher; 
    } 

    public void BeginInvoke(Delegate method, params object[] args) 
    { 
     _dispatcher.BeginInvoke(method, args); 
    } 
} 

私のイベントハンドラはOnMyEventHandlerであり、単一のboolパラメータcaを受け取りますlled myBoolParameter)

[Test] 
public void When_DoSomething_Then_InvokeMyEventHandler() 
{ 
    var dispatcher = new Mock<IDispatcher>(); 

    ClassUnderTest classUnderTest = new ClassUnderTest(dispatcher.Object); 

    Action<bool> OnMyEventHanlder = delegate (bool myBoolParameter) { }; 
    classUnderTest.OnMyEvent += OnMyEventHanlder; 

    classUnderTest.DoSomething(); 

    //verify that OnMyEventHandler is invoked with 'false' argument passed in 
    dispatcher.Verify(p => p.BeginInvoke(OnMyEventHanlder, false), Times.Once); 
} 
0

ディスパッチャをサポートする専用スレッドでテストを実行することはどうですか?

void RunTestWithDispatcher(Action testAction) 
    { 
     var thread = new Thread(() => 
     { 
      var operation = Dispatcher.CurrentDispatcher.BeginInvoke(testAction); 

      operation.Completed += (s, e) => 
      { 
       // Dispatcher finishes queued tasks before shuts down at idle priority (important for TransientEventTest) 
       Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.ApplicationIdle); 
      }; 

      Dispatcher.Run(); 
     }); 

     thread.IsBackground = true; 
     thread.TrySetApartmentState(ApartmentState.STA); 
     thread.Start(); 
     thread.Join(); 
    } 
関連する問題