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

.NET Maui单元测试中读取MAUI资源文件的问题

解决MAUI xUnit测试中读取JSON文件的 NotImplementedInReferenceAssemblyException 问题

问题根源

单元测试项目中引用的Microsoft.Maui.Essentials是引用程序集,仅包含接口定义,没有平台特定的实现逻辑。而FileSystem.OpenAppPackageFileAsync的实际实现依赖平台环境,测试环境无法加载对应实现,因此抛出该异常;真机或模拟器运行时会加载平台专属实现,所以能正常工作。

可行解决方案

方案1:抽象文件读取逻辑,依赖注入解耦

通过抽象接口将文件读取逻辑与ViewModel分离,测试时替换为测试专用实现,这是最符合SOLID原则的方案。

  1. 定义文件服务接口
public interface IFileService
{
    Task<Stream> OpenAppPackageFileAsync(string filename);
}
  1. MAUI项目实现接口
public class MauiFileService : IFileService
{
    public async Task<Stream> OpenAppPackageFileAsync(string filename)
    {
        return await FileSystem.OpenAppPackageFileAsync(filename);
    }
}
  1. 修改ViewModel注入依赖
public class ContactsPageViewModel
{
    private readonly IFileService _fileService;

    // 通过构造函数注入IFileService
    public ContactsPageViewModel(IFileService fileService)
    {
        _fileService = fileService;
    }

    public async Task LoadContacts()
    {
        using var stream = await _fileService.OpenAppPackageFileAsync("contacts.json");
        using var reader = new StreamReader(stream);
        string contents = await reader.ReadToEndAsync();
        List<Contact> distributors = JsonSerializer.Deserialize<List<Contact>>(contents);
        // 后续业务逻辑...
    }
}
  1. MAUI项目注册服务
    在MauiProgram.cs中添加服务注册:
builder.Services.AddSingleton<IFileService, MauiFileService>();
  1. 测试项目实现测试服务
public class TestFileService : IFileService
{
    public async Task<Stream> OpenAppPackageFileAsync(string filename)
    {
        // 需将测试项目中的contacts.json设置为"复制到输出目录"(始终复制/较新时复制)
        string filePath = Path.Combine(AppContext.BaseDirectory, filename);
        return File.OpenRead(filePath);
    }
}

[Fact]
public async Task TestLoadContacts()
{
    var testFileService = new TestFileService();
    var viewModel = new ContactsPageViewModel(testFileService);
    await viewModel.LoadContacts();
    
    // 添加断言逻辑,例如验证联系人列表是否正确加载
}

方案2:直接读取测试项目中的物理文件

如果不想重构ViewModel,可以通过重载方法绕过FileSystem调用。

  1. ViewModel添加重载方法
// 新增接收Stream的重载方法
public async Task LoadContacts(Stream stream)
{
    using var reader = new StreamReader(stream);
    string contents = await reader.ReadToEndAsync();
    List<Contact> distributors = JsonSerializer.Deserialize<List<Contact>>(contents);
}

// 原方法调用重载
public async Task LoadContacts()
{
    using var stream = await FileSystem.OpenAppPackageFileAsync("contacts.json");
    await LoadContacts(stream);
}
  1. 测试中读取物理文件
[Fact]
public async Task TestLoadContacts()
{
    // 测试项目中需将contacts.json设置为"复制到输出目录"
    string filePath = Path.Combine(AppContext.BaseDirectory, "contacts.json");
    using var stream = File.OpenRead(filePath);
    
    var viewModel = new ContactsPageViewModel();
    await viewModel.LoadContacts(stream);
    
    // 添加断言逻辑
}

方案3:使用Mock框架模拟文件系统行为

利用Moq等Mock框架模拟IFileSystem接口的行为(MAUI Essentials提供了IFileSystem接口用于依赖注入)。

  1. ViewModel注入IFileSystem
public class ContactsPageViewModel
{
    private readonly IFileSystem _fileSystem;

    public ContactsPageViewModel(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public async Task LoadContacts()
    {
        using var stream = await _fileSystem.OpenAppPackageFileAsync("contacts.json");
        using var reader = new StreamReader(stream);
        string contents = await reader.ReadToEndAsync();
        List<Contact> distributors = JsonSerializer.Deserialize<List<Contact>>(contents);
    }
}
  1. MAUI项目注册服务
builder.Services.AddSingleton<IFileSystem>(FileSystem.Current);
  1. 测试中Mock IFileSystem
[Fact]
public async Task TestLoadContacts()
{
    // 读取测试项目中的JSON内容
    string filePath = Path.Combine(AppContext.BaseDirectory, "contacts.json");
    string jsonContent = await File.ReadAllTextAsync(filePath);
    var mockStream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent));

    // 模拟IFileSystem的OpenAppPackageFileAsync方法
    var mockFileSystem = new Mock<IFileSystem>();
    mockFileSystem.Setup(fs => fs.OpenAppPackageFileAsync("contacts.json"))
                  .ReturnsAsync(mockStream);

    var viewModel = new ContactsPageViewModel(mockFileSystem.Object);
    await viewModel.LoadContacts();
    
    // 添加断言逻辑,验证解析结果是否符合预期
}

内容的提问来源于stack exchange,提问作者John Hultgren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:23:17