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

如何使用NUnit模拟文件及文件内容(非依赖注入场景)

无需依赖注入实现文件系统模拟的单元测试方案

针对你不想通过依赖注入传入读写器,但需要测试文件读取+验证流程的场景,以下是两种可行方案:

方案一:使用System.IO.Abstractions库模拟文件系统

这个库提供了与.NET原生System.IO完全兼容的抽象层,你可以在类内部默认使用真实文件系统,仅在测试时替换为内存模拟实现,同时保持对外的封装性。

1. 配置业务类

在你的配置管理类中,添加一个仅对测试项目可见的内部构造函数,内部使用IFileSystem处理文件操作:

using System.IO.Abstractions;
using System.Text.Json;
using FluentValidation;

public class ConfigManager
{
    private readonly IFileSystem _fileSystem;

    // 对外公开的无参构造函数,使用真实文件系统
    public ConfigManager() : this(new FileSystem())
    {
    }

    // 内部构造函数,仅测试时使用
    internal ConfigManager(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public ConfigModel LoadMainConfig()
    {
        var configPath = "Config/main_config.json";
        if (!_fileSystem.File.Exists(configPath))
            throw new FileNotFoundException("主配置文件不存在", configPath);

        var content = _fileSystem.File.ReadAllText(configPath);
        var model = JsonSerializer.Deserialize<ConfigModel>(content);
        
        // FluentValidation验证
        new ConfigModelValidator().ValidateAndThrow(model);
        
        return model;
    }

    // 其他读取in/out文件夹配置的方法同理,均使用_fileSystem操作
}

2. 配置测试项目

在测试项目中,通过InternalsVisibleTo特性允许测试代码访问业务类的内部成员(在业务项目的AssemblyInfo.cs或项目文件中添加):

// AssemblyInfo.cs
[assembly: InternalsVisibleTo("YourTestProjectName")]

3. 编写单元测试

使用MockFileSystem模拟目标文件结构和内容:

using System.IO.Abstractions.TestingHelpers;
using FluentValidation;
using NUnit.Framework;

[TestFixture]
public class ConfigManagerTests
{
    [Test]
    public void LoadMainConfig_ValidContent_PassesValidationAndReturnsModel()
    {
        // 构建模拟文件系统
        var mockFs = new MockFileSystem();
        // 添加模拟主配置文件
        mockFs.AddFile("Config/main_config.json", new MockFileData(@"{""AppName"":""TestApp"",""Version"":1.0}"));
        // 添加in文件夹下的模拟配置文件
        mockFs.AddDirectory("Config/in");
        mockFs.AddFile("Config/in/mod1.conf", new MockFileData("<ModConfig><Id>1</Id><Name>Mod1</Name></ModConfig>"));

        // 使用内部构造函数创建测试实例
        var configManager = new ConfigManager(mockFs);

        // 执行测试方法
        var result = configManager.LoadMainConfig();

        // 断言验证结果
        Assert.Multiple(() =>
        {
            Assert.That(result.AppName, Is.EqualTo("TestApp"));
            Assert.That(result.Version, Is.EqualTo(1.0));
        });
    }

    [Test]
    public void LoadMainConfig_InvalidContent_ThrowsValidationException()
    {
        var mockFs = new MockFileSystem();
        // 添加不符合验证规则的内容
        mockFs.AddFile("Config/main_config.json", new MockFileData(@"{""AppName"":"""",""Version"":-1}"));

        var configManager = new ConfigManager(mockFs);

        // 断言抛出验证异常
        Assert.Throws<ValidationException>(() => configManager.LoadMainConfig());
    }
}

方案二:自定义静态委托实现文件操作替换

如果不想引入第三方库,可以自己封装静态委托,在测试时临时替换文件操作逻辑,测试后恢复。

1. 封装文件操作委托

创建一个内部静态类,封装所有需要用到的文件操作方法:

using System.IO;

internal static class FileOps
{
    // 默认指向真实文件系统操作
    internal static Func<string, string> ReadAllText = path => File.ReadAllText(path);
    internal static Func<string, bool> FileExists = path => File.Exists(path);
    internal static Func<string, string[]> GetFiles = path => Directory.GetFiles(path);
    // 根据需要添加其他操作(如读取XML文件、遍历子文件夹等)
}

2. 业务类使用委托

在你的配置管理类中,所有文件操作均通过FileOps委托执行:

public class ConfigManager
{
    public ConfigModel LoadMainConfig()
    {
        var configPath = "Config/main_config.json";
        if (!FileOps.FileExists(configPath))
            throw new FileNotFoundException("主配置文件不存在", configPath);

        var content = FileOps.ReadAllText(configPath);
        var model = JsonSerializer.Deserialize<ConfigModel>(content);
        
        new ConfigModelValidator().ValidateAndThrow(model);
        
        return model;
    }
}

3. 编写单元测试

测试时替换委托逻辑,测试完成后恢复默认实现,避免影响其他测试:

[TestFixture]
public class ConfigManagerTests
{
    [SetUp]
    public void Setup()
    {
        // 每次测试前恢复默认委托
        FileOps.ReadAllText = File.ReadAllText;
        FileOps.FileExists = File.Exists;
    }

    [Test]
    public void LoadMainConfig_ValidContent_ReturnsValidModel()
    {
        // 替换文件存在判断逻辑
        FileOps.FileExists = path => path == "Config/main_config.json";
        // 替换文件读取逻辑
        FileOps.ReadAllText = path => 
        {
            if (path == "Config/main_config.json")
                return @"{""AppName"":""TestApp"",""Version"":1.0}";
            throw new FileNotFoundException();
        };

        var configManager = new ConfigManager();
        var result = configManager.LoadMainConfig();

        Assert.That(result.AppName, Is.EqualTo("TestApp"));
    }
}

两种方案都不需要对外暴露依赖注入接口,完全保持了业务类的封装性,同时实现了文件系统的模拟测试。

内容的提问来源于stack exchange,提问作者Lars Kakavandi-Nielsen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:01:14