使用Autofac时使用C#模拟System.IO.XXX类

时间:2013-01-10 15:55:10

标签: c# moq autofac

我在项目中使用Autofac作为DI容器。现在我已经开始使用Moq创建单元测试了。由于已经编写了业务类的代码,因此我希望避免在Business类中进行重大更改。我在模拟System.IO.XXX类(如FileSystemWatcher,Directory,File,StreamReader等)时遇到问题。主要是因为它们是静态类或者没有任何接口。

//这是我的业务类之一

internal class SpanFileReader : ISpanFileReader
{
// Some private variables
    private string _filePath;
    private readonly ISpanLogger _spanLogger;

#region Public Properties
    // Prorperties....
#endregion

#region Constructor

public SpanFileReader(string filePath)
{
    _filePath = filePath;
    _spanLogger = IocContainer.Instance.Container.Resolve<ISpanLogger>();
}

#endregion

#region Public Methods

public bool ReadSpanRecords(CancellationToken ct)
{
    try
    {
        if (!VerifySpanFile())
            return false;

        _spanFileLines = new List<string>();

        using (var streamReader = new StreamReader(_filePath))
        {
            while (!streamReader.EndOfStream)
            {
                // some logic
            }
        return true;
        }
    }

    catch (OperationCanceledException operationCanceledException)
    {
        _spanLogger.UpdateLog("some message");
        throw;
    } 
    catch (Exception ex)
    {
        _spanLogger.UpdateLog("some message);
        throw;
    }
}

public void MoveFileToErrorFolder(string spanFileName)
{
    var spanFilePath = AppConfiguration.SpanFolderPath + spanFileName;
    var errorFilePath = AppConfiguration.SpanErrorFolderPath + spanFileName;
    try
    {
        if (File.Exists(spanFilePath))
        {
            if (!File.Exists(errorFilePath))
            {
                _spanLogger.UpdateLog("some message");
                File.Move(spanFilePath, errorFilePath);
                _spanLogger.UpdateLog("some message");
            }
            else
            {
                File.Delete(spanFilePath);
                _spanLogger.UpdateLog("some message");
            }
        }
        else
        {
            _spanLogger.UpdateLog("some message");
        }
    }
    catch (Exception ex)
    {
        _spanLogger.UpdateLog("some message");
        throw ex;
    }
}
}

现在我想以这样的方式使用 StreamReader(),我可以使用 Autofac <通过某个界面(例如 IStreamReader )解析其实例/ strong>即可。因此,在为 SpanFileReader()编写单元测试时,我可以在容器中注册 IStreamReader的 Moq实例,并使用它代替实际实例。 我也想用 File()类做类似的事情,这样我就可以提供自己的moq实现,当SUT( SpanFileReader instace)被调用时)经过测试。 有人可以建议一个正确的方法来解决这些情况。

2 个答案:

答案 0 :(得分:1)

你提到了两个问题:

如何模仿StreamReader

  1. 通过复制IStreamReader
  2. 中的方法来创建StreamReader界面
  3. 创建一个实现StreamReaderWrapper
  4. 的包装器类IStreamReader
  5. 在包装器的构造函数中,创建一个StreamReader
  6. 对于每种方法,将调用转发给包装对象。
  7. e.g。

    interface IStreamReader
    {
        string ReadLine();
        // etc...
    }
    
    public class StreamReaderWrapper : IStreamReader
    {
        private StreamReader _streamReader;
    
        public StreamReaderWrapper(string path)
        {
            _streamReader = new StreamReader(path);
        }
    
        public string ReadLine()
        {
            return _streamReader.ReadLine();
        }
    }
    

    现在使用Autofac(或任何工厂/ IoC容器)替换代码中的new StreamReader()。测试时,请返回Mock<IStreamReader>()

    using (var streamReader =
        IocContainer.Instance.Container.Resolve<IStreamReaderWrapper>(_filePath))
    {
        // ...
    }
    

    如何模仿File

    执行与上面相同的操作,而不是创建一个没有构造函数/包装对象的IFileFileWrapper类。

    实例化此包装器的实例以供整个类使用,并在您通常使用File时使用此实例。

    E.g。

    interface IFile
    {
        bool Exists(string name);
        // etc...
    }
    
    public class FileWrapper : IFile
    {
        public bool Exists(string name)
        {
            return File.Exists(name);
        }
    }
    

    然后(来自你的例子):

    public SpanFileReader(string filePath)
    {
        // ...
        _fileWrapper = IocContainer.Instance.Container.Resolve<IFileWrapper>();
    }
    
    public void MoveFileToErrorFolder(string spanFileName)
    {
        // ...
        if (_fileWrapper.Exists(spanFilePath))
        {
           // ...
        }
    }
    

    我发现这是一个非常干净的模式,因为所有的构造函数/方法签名都被保留了。测试不会触及文件系统而且容易被嘲笑是一个巨大的优势,而且速度也快得多。

    一个重要的提示是避免向包装器添加任何逻辑,否则它们也可能需要测试(所以你必须创建一个包装器包装器......)!

    类似File的类也可以通过实例访问,所以你可能想创建单独的接口来单独保存实例和静态方法包装器(IFileStatics?)。

    另一个想法是构建一个包含各种包装的.Net类的库以供将来使用,或者使用各种.NET技术自动创建包装器(例如Castle Dynamic Proxy)。

答案 1 :(得分:0)

查看System.IO.Abstractions https://github.com/tathamoddie/System.IO.Abstractions。我最近才发现这一点,但它对我来说效果很好。

这基本上是一个包含System.IO中类的包装器的库,因此您可以将构造函数注入到类中,并在测试中传递模拟。

例如:

// register in autofac
builder.RegisterType<FileWrapper>().As<FileBase>();

public class MyClass 
{
    private readonly FileBase _file;

    public MyClass(FileBase file) 
    {
         _file = file;
    }

    public void MyMethod()
    {
        _file.Exists("...");
    }

 }