我正在为视图模型编写单元测试。该应用程序使用Caliburn.Micro编写,用于MVVM支持。许多视图模型依赖于Application.Current.Dispatcher,有意将一些代码分配到UI线程中。
要从测试中创建Application对象,我编写了以下类:
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,我不想将另一个模拟对象传递给视图模型。如果可能的话,我想得到一些解决方法。
答案 0 :(得分:3)
我认为你错过了对Application.Run
的号召。你是对的,Application
类的职责之一是在当前正在执行的线程上创建并启动Dispatcher
,但所有这些都发生在调用Run
期间。
这就是麻烦开始的地方:Run
是一个阻止调用,即在Run
退出之前不会执行单元测试。在商店应用中,有一个名为UITestMethod
的特殊属性,但我不认为它在WPF中可用(特别是如果您不使用MSTest)。
那你有什么选择?您可以在与运行单元测试的线程不同的线程上创建应用程序 - 但这将导致对Join
的方法调用,因为您必须查看是否将调用分派给其他线程。这可能会导致慢速单元测试。
您甚至无法在执行单元测试的线程上手动创建调度程序 - 因为它与前面提到的App
类相同:Dispatcher.Run
是阻塞调用。
这就是为什么我建议你为Dispatcher
创建一个抽象并注入它 - 它可以为你节省很多痛苦。
更新环境背景:
在评论中,我提到了Ambient Context作为一种解决方案,它不依赖于将对象注入符合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中详细了解此模式。