如何为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
相关产品推荐
相关产品推荐

