2014-09-05 54 views
1

我正在为视图模型编写单元测试。该应用程序使用Caliburn.Micro为MVVM支持编写。许多视图模型依赖于Application.Current.Dispatcher,目的是将一些代码分派到UI线程中。单元测试时挂在Application.Current.Dispatcher.Invoke(action)

创建应用程序对象从测试中我写了下面的类:

public class AppInitializer { 
    private static Application app; 
    public static void InitApp() { 
     app = app ?? (app = Application.Current ?? new Application()); 
    } 
} 

我现在只是做了每个测试类如下:

[ClassInitialize] 
    public static void InitClass(TestContext ctx) { 
     AppInitializer.InitApp(); 
    } 

不幸的是,第一个电话从视图模型中的Application.Current.Dispatcher挂起我的测试,直到超时。

我不想以某种方式抽象Application.CurrentDispatcher,我不想传入视图模型多一个模拟对象。如果可能的话,我想得到一些解决方法。

回答

3

我想你错过了Application.Run的电话。你是对的,其中Application类的职责之一是在其当前正在执行的线程上创建并启动Dispatcher,但所有这些都发生在调用Run期间。

而这正是麻烦开始了:Run是一个阻塞调用,即你的单元测试之前,不会退出Run执行。在商店应用程序中,有一个称为UITestMethod的特殊属性,但我不认为它在WPF中可用(尤其是如果您未使用MSTest)。

那么你有什么选择?您可以在与您的单元测试运行的线程不同的线程上创建应用程序 - 但这会导致方法调用Join,因为您必须查看是否将您的调用分派到其他线程。这对单元测试速度很慢。

甚至不能在您的单元测试执行的线程上手动创建调度程序 - 因为它与前面提到的App类相同:Dispatcher.Run是阻塞调用。

这就是为什么我建议你为Dispatcher创建一个抽象并注入它 - 它为您节省了很多痛苦。

更新周围语境:

在评论中,我提到了周围语境作为不依赖于注射的对象为符合该Dispatcher抽象的视图模型的解决方案。这是它会是什么样子代码:

public interface IDispatcher 
{ 
    void ExecuteOnUIThread(Action action); 
    // Add whatever methods you need on this interface 
} 

public static class DispatcherContext 
{ 
    // An instance that implements IDispatcher can be accessed via this static property 
    public static IDispatcher Dispatcher { get; set; } 
} 

// Of course you need to write an adapter for the WPF Dispatcher class 

这样你可以为你的单元测试创​​建调度模拟,但仍然能够通过在您的视图模型的静态属性访问此。您可以在http://blogs.msdn.com/b/ploeh/archive/2007/07/23/ambientcontext.aspx或Mark Seemann的优秀书籍Dependency Injection in .NET中了解有关此模式的更多信息。

+0

如果我添加app.Run(),测试在此调用上挂起。 – EngineerSpock 2014-09-05 06:58:04

+0

对不起,我还没有完成我的答案。我已经更新了它。 – feO2x 2014-09-05 07:04:27

+0

不幸的是,我使用无处不在Caliburn的静态方法Execute.OnUITread(Action action)。这将很难嘲笑。 – EngineerSpock 2014-09-05 07:13:08