在 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;
<pre class="brush:php;toolbar:false;">[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";
<pre class="brush:php;toolbar:false;">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;
<p>vm.Account = "test";
Assert.Equal("Account", raisedProp);</p>测试异步命令和响应式逻辑
如果用了 ReactiveUI,重点测
ReactiveCommand的执行、CanExecute 变化、执行结果: 用
TestScheduler控制时间流(适合复杂响应式链) 对
Execute调用后,断言输出属性、服务调用次数、异常等 模拟依赖服务返回 Task 或 Observable,例如用
Observable.Return(...)或
Task.FromResult(...)
例如测试登录失败场景:
var mockAuthService = new Mock<IAuthService>();
mockAuthService.Setup(x => x.LoginAsync(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(false);
<p>var vm = new LoginViewModel(mockAuthService.Object);
vm.Account = "bad";
vm.Pwd = "pass";
await vm.LoginCommand.ExecuteAsync(null);</p><p>Assert.False(vm.IsLoggedIn);</p>基本上就这些。不复杂但容易忽略的是:别在 ViewModel 里做 UI 导航、弹窗、资源加载——那些该交给 View 或专门的导航服务,否则测试会卡住或失败。
