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

在 Avalonia 中对 ViewModel 做单元测试,核心思路是:把 ViewModel 当作普通 C# 类来测,不依赖 UI、不启动窗口、不触发渲染。只要它实现了 INotifyPropertyChanged、用了命令(ICommand)、有业务逻辑或状态流转,就能独立验证。
ViewModel 必须可测试的前提
确保你的 ViewModel 基类已正确支持属性变更通知和命令机制:
- 继承自
ReactiveUI.ViewModel或CommunityToolkit.Mvvm.ObservableObject,或手动实现INotifyPropertyChanged - 使用
RelayCommand/ReactiveCommand/AsyncRelayCommand定义命令,而非直接写事件处理方法 - 避免在 ViewModel 中调用
Application.Current、Dispatcher.UIThread、DialogHost等 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.Mvvm的SetProperty或[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 或专门的导航服务,否则测试会卡住或失败。










