mcpField
M
maui-unit-testing

maui-unit-testing

github.com/davidortinau测试与质量17665 次安装16 周前更新
代码仓库

关于 maui-unit-testing

这项 .NET MAUI 测试技能涵盖单元测试配置、ViewModel 模式和常见陷阱。它帮助开发者将 MAUI 平台 API 隔离在可注入接口后,使 ViewModel 更易测试。

主要功能

  1. 01测试目标框架与项目输出配置建议
  2. 02为 MAUI API 使用可注入接口
  3. 03测试 ViewModel 状态和命令
  4. 04MAUI 服务的模拟策略
  5. 05异步流程与 CI 测试建议

适用场景

  1. 01为 MAUI 应用配置单元测试
  2. 02无需启动 UI 即可测试 ViewModel
  3. 03用 dotnet test 执行回归检查

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/davidortinau/maui-skills --skill maui-unit-testing

安装后在 Claude Code 中输入/maui-unit-testing确认已加载。

附带文件references/unit-testing-api.md

详细介绍

.NET MAUI 单元测试——注意事项与最佳实践

项目模板、xUnit 示例、ViewModel 测试模式和 CLI 命令,请参阅 references/unit-testing-api.md。

⚠️ TFM 陷阱:不要使用特定平台的 TFM


<TargetFramework>net9.0-ios</TargetFramework>


<TargetFrameworks>net9.0;net10.0</TargetFrameworks>

⚠️ OutputType 陷阱:将应用项目用作测试依赖项

如果测试项目引用了应用项目,应用项目会尝试以测试 TFM 的 Exe 类型构建——这会失败。请添加条件 OutputType:


<OutputType>Exe</OutputType>


<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
  <OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'net9.0'">
  <OutputType>Exe</OutputType>
</PropertyGroup>

常见错误

❌ 在 ViewModel 中使用 MAUI 静态 API

直接调用 MAUI 静态 API 的 ViewModel 无法测试:

// ❌ Untestable — Shell.Current requires a running MAUI app
public async Task GoToDetail(int id)
    => await Shell.Current.GoToAsync($"detail?id={id}");

// ✅ Inject an interface — fully testable
public class MyViewModel(INavigationService nav)
{
    public async Task GoToDetail(int id)
        => await nav.GoToAsync($"detail?id={id}");
}

应通过接口封装的静态 API:

  • Shell.Current → INavigationService
  • Application.Current → 完全避免使用
  • SecureStorage.Default → ISecureStorage
  • Connectivity.Current → IConnectivity
❌ 断言 UI 绑定,而不是 ViewModel 状态
// ❌ Testing the binding — fragile, needs a running UI
Assert.Equal("Hello", label.Text);

// ✅ Testing the ViewModel — fast, no platform dependency
Assert.Equal("Hello", viewModel.Title);
Assert.True(viewModel.SaveCommand.CanExecute(null));

MAUI 服务的模拟策略

MAUI 服务模拟策略
ISecureStorageMock<ISecureStorage>——为 GetAsync/SetAsync 设置存根
IPreferencesMock<IPreferences>——为 Get/Set/Remove 设置存根
IConnectivityMock<IConnectivity>——返回 NetworkAccess
IGeolocationMock<IGeolocation>——返回固定的 Location
IFilePickerMock<IFilePicker>——返回 FileResult
IMediaPickerMock<IMediaPicker>——返回 FileResult
Shell 导航通过 INavigationService 抽象
IDispatcher设置存根,让 Dispatch 同步调用操作

架构:优先使用接口,提升可测试性

定义服务接口,让 ViewModel 完全不依赖 MAUI 平台:

// ✅ These make your entire ViewModel layer testable
public interface INavigationService
{
    Task GoToAsync(string route);
    Task GoBackAsync();
}

public interface IDialogService
{
    Task<bool> ConfirmAsync(string title, string message);
}

在 MauiProgram.cs 中注册实现,并将接口注入 ViewModel。

提示

  • ViewModel 中不要使用 Application.Current 或 Shell.Current——通过可注入服务封装
  • 使用 ObservableCollection<T> 和 [ObservableProperty](MVVM Toolkit)管理可测试状态
  • 断言 ViewModel 属性和 CanExecute,而不是 UI 绑定
  • 使用 TaskCompletionSource 测试异步等待流程
  • 在 CI 中运行 dotnet test,及早发现回归问题
  • 设备端测试:使用 xunit.runner.devices 测试真实平台 API(传感器、相机、蓝牙)

检查清单

  • 测试项目目标框架为普通的 net9.0/net10.0(不是特定平台的 TFM)
  • 应用项目针对测试 TFM 设置了条件 OutputType
  • 所有 MAUI 静态 API 都通过可注入接口封装
  • 通过属性和命令测试 ViewModel,而不是 UI 绑定
  • 在测试中模拟 IDispatcher,使其同步调用
  • CI 中的 dotnet test 运行通过

评论

支持 Markdown