如何用TestStack.White测试WPF自定义UserControl的事件触发?
Hey Marco, I’ve tackled similar scenarios with TestStack.White and DevExpress WPF controls before, so let me walk you through a few solid approaches to verify those Loaded/Unloaded events fire correctly.
This is the most straightforward method—extend your custom DevExpress UserControl with public flags that get updated when the target events trigger. You can then check these flags directly in your tests.
- First, update your custom UserControl class to track event triggers:
public class CustomDevExpressUserControl : DevExpress.Xpf.LayoutControl.LayoutControl { // Public flags to track event state public bool LoadedEventTriggered { get; private set; } public bool UnloadedEventTriggered { get; private set; } public CustomDevExpressUserControl() { // Subscribe to the events and update flags Loaded += (sender, e) => LoadedEventTriggered = true; Unloaded += (sender, e) => UnloadedEventTriggered = true; } }
- Then, in your TestStack.White test, retrieve the control and validate the flags after triggering load/unload actions:
// Assume you've already obtained the parent window instance var targetControl = window.Get<CustomDevExpressUserControl>(SearchCriteria.ByAutomationId("MyControlAutomationId")); // Trigger control loading (ensure it's rendered in the visual tree) window.UpdateLayout(); // Verify Loaded event fired Assert.IsTrue(targetControl.LoadedEventTriggered); // Trigger control unloading (e.g., close the parent window or remove the control) window.Close(); // Verify Unloaded event fired Assert.IsTrue(targetControl.UnloadedEventTriggered);
TestStack.White is built on top of Windows UI Automation, so you can subscribe to automation-level events that correspond to WPF’s Loaded/Unloaded lifecycle.
- Use automation event handlers to track when the control is added/removed from the visual tree:
var controlElement = targetControl.AutomationElement; bool loadedFired = false; bool unloadedFired = false; // Subscribe to StructureChanged event to detect when the control is loaded Automation.AddStructureChangedEventHandler( controlElement, TreeScope.Element, (sender, e) => { if (e.StructureChangeType == StructureChangeType.ChildAdded) { loadedFired = true; } }); // Subscribe to WindowClosed event to detect when the control is unloaded Automation.AddAutomationEventHandler( WindowPattern.WindowClosedEvent, controlElement, TreeScope.Element, (sender, e) => { unloadedFired = true; }); // Trigger load action window.UpdateLayout(); Assert.IsTrue(loadedFired); // Trigger unload action window.Close(); Assert.IsTrue(unloadedFired); // Clean up event handlers to avoid memory leaks Automation.RemoveStructureChangedEventHandler(controlElement, ...); Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent, controlElement, ...);
If your UserControl follows the MVVM pattern, you can bind the Loaded/Unloaded events to commands in your view model, then validate the view model’s state in tests.
- In your UserControl’s XAML, bind the events to view model commands:
<UserControl xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol" Loaded="{Binding OnControlLoadedCommand}" Unloaded="{Binding OnControlUnloadedCommand}"> <!-- Your control content here --> </UserControl>
- Update your view model to track command execution:
public class MyControlViewModel : DevExpress.Mvvm.ViewModelBase { public bool LoadedCommandExecuted { get; private set; } public bool UnloadedCommandExecuted { get; private set; } public ICommand OnControlLoadedCommand => new DevExpress.Mvvm.DelegateCommand(() => LoadedCommandExecuted = true); public ICommand OnControlUnloadedCommand => new DevExpress.Mvvm.DelegateCommand(() => UnloadedCommandExecuted = true); }
- In your test, access the view model and validate the flags:
var viewModel = targetControl.DataContext as MyControlViewModel; Assert.IsNotNull(viewModel); // After triggering load Assert.IsTrue(viewModel.LoadedCommandExecuted); // After triggering unload Assert.IsTrue(viewModel.UnloadedCommandExecuted);
内容的提问来源于stack exchange,提问作者Marco Moscato

