WPF OnSelectionChanged方法单元测试参数初始化报错
WPF OnSelectionChanged方法单元测试参数构造问题
问题背景
待测试的OnSelectionChanged为静态业务方法,接收object类型sender(生产环境传入TreeView控件实例)、ItemSelectionChangedEventArgs类型参数(用于获取当前选中的文件夹项),返回ObservableCollection<Folder>类型的目录/文件集合,方法实现如下:
public static ObservableCollection<Folder> OnSelectionChanged(object sender, ItemSelectionChangedEventArgs itemSelection) { if (sender == null) { throw new ArgumentNullException("Can't be null", nameof(sender)); } if (itemSelection == null) { throw new ArgumentNullException("Can't be null", nameof(itemSelection)); } var selected = itemSelection.AddedItems[0] as Folder; DirectoryInfo dirInfo = new DirectoryInfo(selected.FullName.ToString()); ObservableCollection<Folder> directories = new ObservableCollection<Folder>(); directories = AddDirectoryInfo(directories, dirInfo); directories = AddFileInfo(directories, dirInfo); return directories; }
编写测试时初始化参数出现两类异常:
- 实例化SfTreeView作为sender时抛出:
System.InvalidOperationException: 'The calling thread should be STA, since most UI components require it.' - 实例化
ItemSelectionChangedEventArgs作为事件参数时抛出:System.NullReferenceException: 'Object reference not set to an instance of an object.'
原测试代码如下:
private readonly string _folderName = "C:\\NewTestFolder"; [TestMethod()] public void OnSelectionChangedTest() { var sender = new SfTreeView() { Name = "treeSize" }; var itemSelection = new ItemSelectionChangedEventArgs() { AddedItems = { new Folder() { FullName = _folderName, HasChildrenNodes = false, Modified = DateTime.Now.ToString(), Name = "NewTestFolder", Size = "0", Type = "Folder" } }, RemovedItems = { new Folder() { FullName = "" } } }; var result = FolderView.OnSelectionChanged(sender, itemSelection); }
错误原因
- STA线程报错原因:WPF及第三方UI控件(如Syncfusion的SfTreeView)必须在STA线程下创建,而单元测试默认运行在MTA线程中,直接实例化UI控件就会触发线程模式异常。实际上查看业务方法逻辑可以发现,方法内仅对sender做了非空校验,没有访问sender的任何属性、方法,完全不需要传入真实的SfTreeView实例。
- 空引用报错原因:
ItemSelectionChangedEventArgs的AddedItems、RemovedItems属性默认值为null,使用集合初始化器语法直接向这两个属性添加项时,本质是对null值调用Add()方法,必然触发空引用异常。同时这类事件参数通常没有暴露公开的无参构造逻辑,直接new实例本身就不符合类型设计预期。
修复方案
1. 绕开UI控件实例化解决STA问题
单元测试只需要满足方法的前置校验要求即可,不需要完全模拟生产环境的入参,sender参数传入任意非null对象就能通过校验,比如直接传new object(),完全不需要实例化重型UI控件,从根源避免STA线程问题。
2. 正确构造事件参数
推荐使用Mock框架伪造事件参数实例,不需要调用类型的真实构造函数,也不需要手动初始化内部集合,以Moq框架为例,测试代码修改如下:
private readonly string _folderName = "C:\\NewTestFolder"; [TestMethod()] public void OnSelectionChangedTest() { // 传入任意非null对象作为sender,通过非空校验即可 var sender = new object(); // Mock构造事件参数 var mockArgs = new Mock<ItemSelectionChangedEventArgs>(); var testFolder = new Folder() { FullName = _folderName, HasChildrenNodes = false, Modified = DateTime.Now.ToString(), Name = "NewTestFolder", Size = "0", Type = "Folder" }; // 配置AddedItems返回包含测试文件夹的集合 mockArgs.SetupGet(a => a.AddedItems).Returns(new List<object> { testFolder }); var itemSelection = mockArgs.Object; // 注意:测试前需确保C:\NewTestFolder路径真实存在,否则DirectoryInfo访问会抛异常 var result = FolderView.OnSelectionChanged(sender, itemSelection); // 补充断言逻辑校验返回结果 Assert.IsNotNull(result); }
如果你使用的ItemSelectionChangedEventArgs版本允许直接给AddedItems、RemovedItems属性赋值,也可以手动先初始化集合再添加项,不需要Mock:
var itemSelection = new ItemSelectionChangedEventArgs() { // 先初始化集合实例,再添加元素 AddedItems = new List<object> { new Folder() { FullName = _folderName, HasChildrenNodes = false, Modified = DateTime.Now.ToString(), Name = "NewTestFolder", Size = "0", Type = "Folder" } }, RemovedItems = new List<object> { new Folder() { FullName = "" } } };
补充提示:测试执行前需要确认测试用的目录路径真实存在,否则方法内
DirectoryInfo读取目录信息时会抛出目录不存在异常,导致测试失败。
内容的提问来源于stack exchange,提问作者Tort Sweet
相关产品推荐
相关产品推荐

