Avalonia怎么在单元测试中测试ViewModel Avalonia单元测试教程

17次阅读

在 Avalonia 中测试 ViewModel 的核心是将其视为普通 C# 类,不依赖 ui 组件,通过验证属性变更通知、命令执行及状态变化来确保逻辑正确,依赖服务需接口注入并用 Moq 等模拟。

Avalonia怎么在单元测试中测试ViewModel Avalonia单元测试教程

在 Avalonia 中对 ViewModel 做单元测试,核心思路是:把 ViewModel 当作普通 C# 类来测,不依赖 UI、不启动窗口、不触发渲染。只要它实现了 INotifyPropertyChanged、用了命令(ICommand)、有业务逻辑或状态流转,就能独立验证。

ViewModel 必须可测试的前提

确保你的 ViewModel 基类已正确支持属性变更通知和命令机制:

  • 继承reactiveUI.ViewModelCommunityToolkit.Mvvm.ObservableObject,或手动实现 INotifyPropertyChanged
  • 使用 RelayCommand / ReactiveCommand / AsyncRelayCommand 定义命令,而非直接写事件处理方法
  • 避免在 ViewModel 中调用 application.CurrentDispatcher.UIThreadDialogHost 等 Avalonia UI 特定对象
  • 依赖的服务(如数据访问、网络请求)应通过接口注入,方便在测试中用 Moq 或 Fake 替换

写一个基础单元测试(以 CommunityToolkit.Mvvm 为例)

假设你有如下 ViewModel:

public partial class LoginViewModel : ObservableObject {     [ObservableProperty]     private string _account; 
[ObservableProperty] private string _pwd;  [ICommand] public void Login() {     if (!string.IsNullOrWhiteSpace(Account) && Account.Length > 2)         IsLoggedIn = true; }  [ObservableProperty] private bool _isLoggedIn;

}

对应测试代码(用 xUnit + Moq):

  • 新建 xUnit 测试项目,引用被测项目和 CommunityToolkit.Mvvm
  • 创建测试类,实例化 ViewModel
  • 验证属性赋值后是否触发通知
  • 调用命令后检查状态变化

示例:

[Fact] public void Login_WhenAccountValid_SetsIsLoggedInToTrue() {     var vm = new LoginViewModel();     vm.Account = "admin";     vm.Pwd = "123"; 
vm.Login();  Assert.True(vm.IsLoggedIn);

}

测试属性变更通知(INPC)

关键是要验证 PropertyChanged 事件是否被正确触发:

  • 订阅 PropertyChanged 事件,记录触发的属性名
  • 修改属性,检查是否收到对应事件
  • 推荐用 CommunityToolkit.MvvmSetProperty[ObservableProperty],它们默认保障通知逻辑正确

简单验证方式:

var vm = new LoginViewModel(); string? raisedProp = null; vm.PropertyChanged += (_, e) => raisedProp = e.PropertyName; 

vm.Account = "test"; Assert.Equal("Account", raisedProp);

测试异步命令和响应式逻辑

如果用了 ReactiveUI,重点测 ReactiveCommand 的执行、CanExecute 变化、执行结果:

  • TestScheduler 控制时间流(适合复杂响应式链)
  • Execute 调用后,断言输出属性、服务调用次数、异常等
  • 模拟依赖服务返回 Task 或 Observable,例如用 Observable.Return(...)Task.FromResult(...)

例如测试登录失败场景:

var mockAuthService = new Mock(); mockAuthService.Setup(x => x.Loginasync(It.IsAny(), It.IsAny()))                .ReturnsAsync(false); 

var vm = new LoginViewModel(mockAuthService.Object); vm.Account = "bad"; vm.Pwd = "pass"; await vm.LoginCommand.ExecuteAsync(null);

Assert.False(vm.IsLoggedIn);

基本上就这些。不复杂但容易忽略的是:别在 ViewModel 里做 UI 导航、弹窗、资源加载——那些该交给 View 或专门的导航服务,否则测试会卡住或失败。

text=ZqhQzanResources