【问题标题】:xUnit Mock request new instance from IoC containerxUnit Mock 从 IoC 容器请求新实例
【发布时间】:2019-09-27 21:16:49
【问题描述】:

我正在为使用 SSH.Net 上传文件的方法编写一些单元测试。

该项目是一个 WPF 应用程序,使用 Caliburn.Micro 作为 MVVM 框架,并在我正在测试的类的构造函数中注入以下对象:

    private IFileTransferManager _fileTransferManager;

    public FileUploadViewModel(IFileTransferManager fileTransferManager) : base(eventAggregator)
    {
        _fileTransferManager = fileTransferManager;
    }

在测试项目中我正在模拟 IFileTransferManager:

    private Mock<IFileTransferManager> _fileTransferManager = new Mock<IFileTransferManager>();

但现在我明白了,在代码中我需要从 IoC 容器请求 IFileTransferManager 的新实例,IoC 是 Caliburn.Micro 中的一个静态类:

    _fileTransferManager = IoC.Get<IFileTransferManager>();

    await _fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false);

我如何重构上述代码以使其可测试,因为目前它在 Caliburn.Micro.dll 中抛出 System.InvalidOperationException,因为我正在重新实例化 _fileTransferManager

【问题讨论】:

  • 使用 Func 委托注入工厂。我很好奇,如果已经通过构造函数注入了一个新实例,为什么还需要一个新实例?
  • 理想情况下,您永远不必像这样直接调用您的 IoC 容器。如果该类在构造函数中注入了IFileTransferManager,那么您不必担心它,因为在实例化该类以进行测试时,您只需直接将其传递给您的模拟。
  • @Nkosi 这个问题与另一个问题有关stackoverflow.com/questions/57902034/… 我也在使用进度条来跟踪上传进度,但是如果我同时进行多次上传,进度条就不起作用了适当地。我找到的解决方法是为我上传的每个文件请求一个新的 IFileTransferManager。

标签: c# unit-testing moq xunit


【解决方案1】:

我可能会做这样的事情,假设还有其他限制因素意味着你想尽可能少地改变关于课程的外部细节(注意:我还没有测试过,所以可能需要稍微调整一下)

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...

我推荐这种方法是因为:

  • 它提供了一种覆盖类获取IFileTransferManager 进行测试的方式
  • 如果不使用此覆盖,它会“回退”到默认实现,保留原始行为 - 您根本不需要从非测试代码更改对此类的现有调用
  • 它不会更改构造函数或添加新构造函数,我认为这是一个问题,因为您不只是将 IFileTransferManager 的实例注入其中。

一个改进可能是设置internal,这将阻止其他项目设置此方法,然后可以通过InternalVisibleTo 或类似方法公开它,但我试图保持范围相当紧凑.. .

【讨论】:

  • 不客气,尽管我应该指出,这确实意味着您的类在使用时对 IFileTransferManager 有非明显的依赖;从长远来看,您可能会更好地直接通过构造函数注入它,然后您可以在实例化类进行测试时直接使用您的模拟。有时,尽管在现实世界中,我们不得不使用可行的解决方案,而不是那些也很优雅的解决方案
【解决方案2】:

使用Func&lt;TResult&gt; 委托注入工厂。

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>();

对于单元测试,可以轻松创建一个函数来提供测试用例所需的尽可能多的模拟

【讨论】:

    猜你喜欢
    • 2018-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2018-05-24
    • 2013-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多