Вам либо понадобится отдельный поток для управления моделью представления, либо для этого вам потребуется выполнить код в потоке диспетчера.Я предпочитаю последнее, но любой из них будет работать.Первый потребует от вас осторожности при использовании диспетчера для перенаправления некоторых операций в поток пользовательского интерфейса;для просмотра свойств модели это не нужно, потому что WPF сделает это автоматически, но другие вещи, такие как прямые вызовы методов объекта пользовательского интерфейса - например, Window.Close()
- do.
Вот пример того, что вы можетеиспользуйте поток диспетчера для выполнения всего кода тестирования:
[TestMethod]
public void TestWpfApp()
{
Thread thread = new Thread(() =>
{
var application = new App();
Application.ResourceAssembly = System.Reflection.Assembly.GetExecutingAssembly();
application.InitializeComponent();
application.Dispatcher.InvokeAsync(() =>
{
_TestApplication(application);
}, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
application.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
private static async void _TestApplication(Application application)
{
Window window = application.MainWindow;
ViewModel viewModel = (ViewModel)window.DataContext;
await Task.Delay(TimeSpan.FromSeconds(5));
viewModel.Text = "Hello World!";
await Task.Delay(TimeSpan.FromSeconds(5));
window.Close();
}
Базовая структура состоит в том, чтобы настроить поток, подходящий для запуска пользовательского интерфейса WPF (это должен быть поток STA, и вы не должнывозиться с потоком модульного теста, поэтому для этого необходимо создать новый поток), а затем в этом потоке выполнить обычную настройку WPF плюс очередь через InvokeAsync()
, чтобы диспетчер вызвал основной метод тестирования, чтобыон начинает выполняться после запуска кода WPF.
Естественно, в этом примере предполагается, что класс ViewModel
имеет свойство Text
, а для свойства DataContext
главного окна задан экземпляр этого ViewModel
.В моем примере программы я просто связал свойство Text
со свойством TextBlock.Text
.Очевидно, вы можете делать с вашей моделью представления все, что захотите.
Обратите внимание, что мне пришлось явно установить Application.ResourceAssembly
.В Visual Studio Community 2017, который я сейчас использую, инфраструктура модульного тестирования запускает текст в контексте, где Assembly.GetEntryAssembly()
возвращает null
, что нарушает загрузку ресурсов WPF.Установка этого явно исправляет это (я использую Assembly.GetExecutingAssembly()
, потому что я поместил код модульного теста в ту же сборку с моей программой WPF примера… очевидно, если вы держите их в разных сборках, вам нужно будет найти правильную сборкукаким-то другим способом).
В моем тестировании использование System.Windows.Threading.DispatcherPriority.ApplicationIdle
при вызове Dispatch.InvokeAsync()
строго не требовалось.Я обнаружил, что свойства MainWindow
и DataContext
инициализированы нормально.Но я предпочитаю явно ждать ApplicationIdle
, просто чтобы убедиться, что они полностью инициализированы и что сама программа WPF готова начать принимать любые входные данные, которые вы имеете в виду для ваших тестов.