在使用autofacc时,用c#模拟System.IO.XXX类
本文关键字:System IO XXX 模拟 autofacc | 更新日期: 2023-09-27 17:52:13
我在一个项目中使用Autofac作为DI容器。现在我已经开始使用Moq创建单元测试了。由于业务类的代码已经写好了,所以我想避免在业务类中进行重大更改。我在模拟System.IO.XXX类(如FileSystemWatcher, Directory, File, StreamReader等)时面临问题。主要是因为它们要么是静态类,要么没有任何接口。
//这是我的一个business类的样子
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()以这样一种方式,我可以通过一些接口(说IStreamReader)使用Autofac来解决它的实例。因此,在编写单元测试SpanFileReader()时,我可以在容器中注册IStreamReader的 Moq实例,并使用它而不是实际实例。我想与File()类做类似的事情,这样我就可以提供我自己的moq实现,当SUT (SpanFileReader实例)被测试时调用。有人能建议一个正确的方法来处理这些情况吗?
你提到两个问题:
如何模拟StreamReader
- 通过复制
StreamReader
的方法创建IStreamReader
接口。 - 创建一个包装器类
StreamReaderWrapper
实现IStreamReader
。 - 在包装器的构造函数中创建一个
StreamReader
。 - 对于每个方法,将调用转发给包装对象。
。
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
和上面一样,创建一个IFile
和FileWrapper
类,没有构造函数/包装对象。
实例化这个包装器的实例供整个类使用,并在通常使用File
的地方使用这个实例。
。
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动态代理)自动创建包装器。
查看System.IO.Abstractions https://github.com/tathamoddie/System.IO.Abstractions。我最近才发现这一点,但它对我来说非常有效。
这基本上是一个包含System中类的包装器的库。因此,您可以将它们构造函数注入到您的类中,并在测试中通过mock。
例如:// 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("...");
}
}