xUnit Mock 从 IoC 容器请求新实例
xUnit Mock request new instance from IoC container
我正在为使用 SSH.Net.
上传文件的方法编写一些单元测试
该项目是一个 WPF 应用程序,使用 Caliburn.Micro 作为 MVVM 框架,还在我正在测试的 class 的构造函数中注入以下对象:
private IFileTransferManager _fileTransferManager;
public FileUploadViewModel(IFileTransferManager fileTransferManager) : base(eventAggregator)
{
_fileTransferManager = fileTransferManager;
}
在测试项目中我正在模拟 IFileTransferManager:
private Mock<IFileTransferManager> _fileTransferManager = new Mock<IFileTransferManager>();
但是现在我说到重点了,在代码中我需要从 IoC 容器请求一个新的 IFileTransferManager 实例,IoC 在 Caliburn.Micro 中是一个静态 class:
_fileTransferManager = IoC.Get<IFileTransferManager>();
await _fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false);
我如何重构上面的代码以使其可测试,因为目前它在 Caliburn.Micro.dll 中抛出 System.InvalidOperationException
因为我正在重新实例化 _fileTransferManager
?
使用 Func<TResult>
委托注入工厂。
private readonly Func<IFileTransferManager> fileTransferManagerFactory;
public FileUploadViewModel(Func<IFileTransferManager> fileTransferManagerFactory) : base(eventAggregator) {
this.fileTransferManagerFactory = fileTransferManagerFactory;
}
这将允许在上传时根据需要创建尽可能多的实例
//get an instance using factory delegate
var fileTransferManager = fileTransferManagerFactory();
await fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false); IoC.Get<IFileTransferManager>();
对于单元测试,可以轻松创建一个函数来提供测试用例所需的尽可能多的模拟
我可能会做这样的事情,假设有其他限制因素意味着你想尽可能少地改变 class 的外部细节(注意:我没有测试过这个所以可能有稍微调整一下)
public class ClassIAmTesting
{
//Have a Func to fetch a file manager...
private Func<IFileTransferManager> _filemgr = null;
//Have a property which we'll use in this class to get the File manager
public Func<IFilterTransferManager> GetFileManager
{
get
{
//If we try to use this property for the first time and it's not set,
//then set it to the default implementation.
if (_fileMgr == null)
{
_fileMgr = () => IoC.Get<IFileTransferManager>();
}
return _fileMgr;
}
set
{
//allow setting of the function which returns an IFileTransferManager
if (_fileMgr == null)
{
_fileMgr = value;
}
}
}
//this is the method you ultimately want to test...
public async Task<bool> SomeMethodIAmTesting()
{
//don't do this any more:
//_fileTransferManager = IoC.Get<IFileTransferManager>();
//instead do this.
_fileTransferManager = GetFileManager();
await _fileTransferManager
.UploadFile(connection, file.FullName, destinationPath)
.ConfigureAwait(false);
return true;
}
}
那么在你的测试中:
Mock<IFileTransferManager> _fileTransferManager = new Mock<IFileTransferManager>();
var cut = new ClassIAmTesting();
//not used Moq for a long time, but think you have to access .Object to get to the
//actual implementation of IFileTransferManager?
cut.GetFileManager = () => _fileTransferManager.Object;
//Do actual tests..
var result = cut.SomeMethodIAmTesting();
//Do assertions...
我建议采用这种方法,因为:
- 它提供了一种方法来覆盖 class 获取
IFileTransferManager
进行测试的方式
- 它 'falls back' 如果不使用此覆盖,则为默认实现,保留原始行为 - 您根本不需要从非测试代码更改对此 class 的现有调用
- 它不会更改构造函数或添加新构造函数,我假设是个问题,因为您不只是将 IFileTransferManager 的实例注入其中。
一个改进可能是设置 internal
以防止其他项目设置此方法,然后可以通过 InternalVisibleTo
或类似方式公开它,但我正在努力保持范围相当紧凑...
我正在为使用 SSH.Net.
上传文件的方法编写一些单元测试该项目是一个 WPF 应用程序,使用 Caliburn.Micro 作为 MVVM 框架,还在我正在测试的 class 的构造函数中注入以下对象:
private IFileTransferManager _fileTransferManager;
public FileUploadViewModel(IFileTransferManager fileTransferManager) : base(eventAggregator)
{
_fileTransferManager = fileTransferManager;
}
在测试项目中我正在模拟 IFileTransferManager:
private Mock<IFileTransferManager> _fileTransferManager = new Mock<IFileTransferManager>();
但是现在我说到重点了,在代码中我需要从 IoC 容器请求一个新的 IFileTransferManager 实例,IoC 在 Caliburn.Micro 中是一个静态 class:
_fileTransferManager = IoC.Get<IFileTransferManager>();
await _fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false);
我如何重构上面的代码以使其可测试,因为目前它在 Caliburn.Micro.dll 中抛出 System.InvalidOperationException
因为我正在重新实例化 _fileTransferManager
?
使用 Func<TResult>
委托注入工厂。
private readonly Func<IFileTransferManager> fileTransferManagerFactory;
public FileUploadViewModel(Func<IFileTransferManager> fileTransferManagerFactory) : base(eventAggregator) {
this.fileTransferManagerFactory = fileTransferManagerFactory;
}
这将允许在上传时根据需要创建尽可能多的实例
//get an instance using factory delegate
var fileTransferManager = fileTransferManagerFactory();
await fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false); IoC.Get<IFileTransferManager>();
对于单元测试,可以轻松创建一个函数来提供测试用例所需的尽可能多的模拟
我可能会做这样的事情,假设有其他限制因素意味着你想尽可能少地改变 class 的外部细节(注意:我没有测试过这个所以可能有稍微调整一下)
public class ClassIAmTesting
{
//Have a Func to fetch a file manager...
private Func<IFileTransferManager> _filemgr = null;
//Have a property which we'll use in this class to get the File manager
public Func<IFilterTransferManager> GetFileManager
{
get
{
//If we try to use this property for the first time and it's not set,
//then set it to the default implementation.
if (_fileMgr == null)
{
_fileMgr = () => IoC.Get<IFileTransferManager>();
}
return _fileMgr;
}
set
{
//allow setting of the function which returns an IFileTransferManager
if (_fileMgr == null)
{
_fileMgr = value;
}
}
}
//this is the method you ultimately want to test...
public async Task<bool> SomeMethodIAmTesting()
{
//don't do this any more:
//_fileTransferManager = IoC.Get<IFileTransferManager>();
//instead do this.
_fileTransferManager = GetFileManager();
await _fileTransferManager
.UploadFile(connection, file.FullName, destinationPath)
.ConfigureAwait(false);
return true;
}
}
那么在你的测试中:
Mock<IFileTransferManager> _fileTransferManager = new Mock<IFileTransferManager>();
var cut = new ClassIAmTesting();
//not used Moq for a long time, but think you have to access .Object to get to the
//actual implementation of IFileTransferManager?
cut.GetFileManager = () => _fileTransferManager.Object;
//Do actual tests..
var result = cut.SomeMethodIAmTesting();
//Do assertions...
我建议采用这种方法,因为:
- 它提供了一种方法来覆盖 class 获取
IFileTransferManager
进行测试的方式 - 它 'falls back' 如果不使用此覆盖,则为默认实现,保留原始行为 - 您根本不需要从非测试代码更改对此 class 的现有调用
- 它不会更改构造函数或添加新构造函数,我假设是个问题,因为您不只是将 IFileTransferManager 的实例注入其中。
一个改进可能是设置 internal
以防止其他项目设置此方法,然后可以通过 InternalVisibleTo
或类似方式公开它,但我正在努力保持范围相当紧凑...