You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为C# .NET MAUI的CreateOrUpdate方法编写单元测试并验证跳转

.NET MAUI ViewModel中CreateOrUpdate方法的单元测试实现方案

一、测试数据库新增/更新逻辑

要验证数据库操作的正确性,核心是Mock数据访问层,避免操作真实SQLite数据库,推荐用Moq配合xUnit完成测试。

1. 抽象数据访问接口

先通过接口隔离数据访问依赖,确保ViewModel不直接依赖SQLite具体实现:

public interface IOrganisationRepository
{
    Task AddOrganisationAsync(Organisation entity);
    Task UpdateOrganisationAsync(Organisation entity);
}

ViewModel通过构造函数注入该接口,而非直接实例化SQLite连接。

2. 编写单元测试用例

分别覆盖Create和Update两种场景:

public class OrganisationFormViewModelTests
{
    private readonly Mock<IOrganisationRepository> _mockRepo;
    private readonly OrganisationFormViewModel _viewModel;

    public OrganisationFormViewModelTests()
    {
        _mockRepo = new Mock<IOrganisationRepository>();
        // 提前注入导航服务Mock,后续测试跳转时会用到
        _viewModel = new OrganisationFormViewModel(_mockRepo.Object, Mock.Of<INavigationService>());
    }

    [Fact]
    public async Task CreateOrUpdate_WhenIntentIsCreate_CallsAddMethod()
    {
        // 准备测试数据
        _viewModel.Intent = IntentType.Create;
        _viewModel.TargetOrganisation = new Organisation { Name = "测试组织" };

        // 执行目标方法
        await _viewModel.CreateOrUpdateCommand.ExecuteAsync(null);

        // 验证是否正确调用了新增方法
        _mockRepo.Verify(r => r.AddOrganisationAsync(It.Is<Organisation>(o => o.Name == "测试组织")), Times.Once);
    }

    [Fact]
    public async Task CreateOrUpdate_WhenIntentIsUpdate_CallsUpdateMethod()
    {
        // 准备测试数据
        _viewModel.Intent = IntentType.Update;
        _viewModel.TargetOrganisation = new Organisation { Id = 1, Name = "更新后的组织" };

        // 执行目标方法
        await _viewModel.CreateOrUpdateCommand.ExecuteAsync(null);

        // 验证是否正确调用了更新方法
        _mockRepo.Verify(r => r.UpdateOrganisationAsync(It.Is<Organisation>(o => o.Id == 1 && o.Name == "更新后的组织")), Times.Once);
    }
}

二、测试页面跳转功能

页面跳转可以测试,但必须抽象导航逻辑,不能直接依赖MAUI原生导航对象(如Shell.Current)。

1. 封装导航服务

定义导航接口,将跳转逻辑封装到服务中:

public interface INavigationService
{
    Task NavigateToOrganisationListAsync();
}

在ViewModel中注入该接口,替换原来的直接导航代码:

public class OrganisationFormViewModel : BaseViewModel
{
    private readonly IOrganisationRepository _repo;
    private readonly INavigationService _navigationService;

    public OrganisationFormViewModel(IOrganisationRepository repo, INavigationService navigationService)
    {
        _repo = repo;
        _navigationService = navigationService;
        CreateOrUpdateCommand = new AsyncCommand(ExecuteCreateOrUpdate);
    }

    public IAsyncCommand CreateOrUpdateCommand { get; }
    public IntentType Intent { get; set; }
    public Organisation TargetOrganisation { get; set; }

    private async Task ExecuteCreateOrUpdate()
    {
        if (Intent == IntentType.Create)
            await _repo.AddOrganisationAsync(TargetOrganisation);
        else
            await _repo.UpdateOrganisationAsync(TargetOrganisation);

        // 通过导航服务完成跳转
        await _navigationService.NavigateToOrganisationListAsync();
    }
}

2. 编写导航测试用例

通过Mock导航服务,验证跳转方法是否被正确调用:

[Fact]
public async Task CreateOrUpdate_AfterDatabaseOperation_TriggersNavigation()
{
    // 准备Mock导航服务
    var mockNavigation = new Mock<INavigationService>();
    var viewModel = new OrganisationFormViewModel(Mock.Of<IOrganisationRepository>(), mockNavigation.Object);
    viewModel.Intent = IntentType.Create;
    viewModel.TargetOrganisation = new Organisation();

    // 执行目标方法
    await viewModel.CreateOrUpdateCommand.ExecuteAsync(null);

    // 验证导航方法是否被调用
    mockNavigation.Verify(n => n.NavigateToOrganisationListAsync(), Times.Once);
}

三、核心注意事项

  • 单元测试仅验证ViewModel的业务逻辑,所有外部依赖(数据库、导航等)必须Mock,避免真实环境干扰测试结果。
  • 禁止在测试中操作真实SQLite数据库,既影响测试速度,又容易造成数据污染。
  • 若使用MAUI Shell路由,可在INavigationService的实现类中封装Shell.Current.GoToAsync("///OrganisationList"),测试时无需关注具体路由,只需验证接口方法的调用情况。

内容的提问来源于stack exchange,提问作者Leah Fernández

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 08:15:28