使用Rhino Mock单元测试DelegateCommand时出现NullReferenceException如何解决
问题原因及修复方案
直接报错原因
你当前抛出NullReferenceException是两个低级错误直接导致的:
- 你Mock的是
IConvertFileDialogViewModel接口,Rhino Mock生成的模拟对象所有属性默认值都是null,你没有给SelectInputFolderCommand设置模拟值就直接调用Execute(),自然触发空引用 - 接口定义和实现不匹配:你贴出的接口名是
IConvertInterviewDialogViewModel,但ViewModel实现的是IConvertFileDialogViewModel,命名不一致会导致后续依赖注入、Mock匹配全部失效
核心逻辑错误
你的单元测试逻辑完全走偏了:
- 你要测试的是
ConvertFileDialogViewModel的实际业务逻辑,完全不需要Mock ViewModel本身,Mock的是ViewModel的外部依赖 - 你的ViewModel直接在方法里new了
FolderBrowserDialogUI组件,单元测试运行时没有UI上下文,就算不Mock直接实例化ViewModel跑测试,也会弹出系统文件夹选择框,根本做不到自动化运行
完整修复步骤
1. 统一接口命名,抽象UI依赖
先把文件夹选择逻辑抽象成服务接口,解耦ViewModel和UI组件:
// 统一后的ViewModel接口 public interface IConvertFileDialogViewModel { string InputFolderPath { get; set; } DelegateCommand SelectInputFolderCommand { get; set; } } // 文件夹选择服务接口 public interface IFolderBrowserService { // 返回用户选择的路径,取消则返回null string ShowDialog(); } // 实际运行时的服务实现 public class FolderBrowserService : IFolderBrowserService { public string ShowDialog() { using var fbd = new FolderBrowserDialog(); return fbd.ShowDialog() == DialogResult.OK ? fbd.SelectedPath : null; } }
2. 改造ViewModel,使用依赖注入
把服务通过构造函数注入到ViewModel,不要直接new UI组件:
public class ConvertFileDialogViewModel : IConvertFileDialogViewModel, INotifyPropertyChanged { private readonly IFolderBrowserService _folderBrowserService; private string _inputFolderPath; public DelegateCommand SelectInputFolderCommand { get; set; } public ConvertFileDialogViewModel(IFolderBrowserService folderBrowserService) { _folderBrowserService = folderBrowserService ?? throw new ArgumentNullException(nameof(folderBrowserService)); SelectInputFolderCommand = new DelegateCommand(SelectInputFolderDialog); } public string InputFolderPath { get => _inputFolderPath; set { _inputFolderPath = value; OnPropertyChanged(nameof(InputFolderPath)); } } public void SelectInputFolderDialog() { var selectedPath = _folderBrowserService.ShowDialog(); if (!string.IsNullOrWhiteSpace(selectedPath)) { InputFolderPath = selectedPath; } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
3. 编写正确的单元测试
这次只Mock外部依赖IFolderBrowserService,ViewModel直接实例化测试:
[TestMethod] public void SelectInputFolderCommand_UserSelectsValidPath_InputFolderPathUpdated() { // 模拟用户选择了有效路径 var mockFolderService = MockRepository.GenerateMock<IFolderBrowserService>(); const string testPath = @"C:\TestFolder"; mockFolderService.Stub(s => s.ShowDialog()).Return(testPath); var viewModel = new ConvertFileDialogViewModel(mockFolderService); viewModel.SelectInputFolderCommand.Execute(null); // 断言路径正确更新,服务被调用一次 Assert.AreEqual(testPath, viewModel.InputFolderPath); mockFolderService.AssertWasCalled(s => s.ShowDialog(), opt => opt.Repeat.Once()); } [TestMethod] public void SelectInputFolderCommand_UserCancelsSelect_InputFolderPathUnchanged() { // 模拟用户点了取消,返回null var mockFolderService = MockRepository.GenerateMock<IFolderBrowserService>(); mockFolderService.Stub(s => s.ShowDialog()).Return(null); var viewModel = new ConvertFileDialogViewModel(mockFolderService); const string originalPath = @"C:\OldPath"; viewModel.InputFolderPath = originalPath; viewModel.SelectInputFolderCommand.Execute(null); // 断言路径没有变化 Assert.AreEqual(originalPath, viewModel.InputFolderPath); }
内容的提问来源于stack exchange,提问作者MakkaCha
相关产品推荐
相关产品推荐

