xUnit Mock запрашивает новый экземпляр из контейнера IoC - PullRequest
0 голосов
/ 27 сентября 2019

Я пишу некоторые модульные тесты для метода, который загружает файл с использованием 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>();

Но теперь я дошел до того, что в коде мне нужно спроситьдля нового экземпляра IFileTransferManager из контейнера IoC, IoC является статическим классом в Caliburn.Micro:

    _fileTransferManager = IoC.Get<IFileTransferManager>();

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

Как я могу реорганизовать приведенный выше код, чтобы сделать его тестируемым, потому что в настоящее время он выдает System.InvalidOperationException в Caliburn.Micro.dll из-за того, что я восстанавливаю _fileTransferManager?

Ответы [ 2 ]

1 голос
/ 27 сентября 2019

Я бы, вероятно, сделал что-то подобное, предполагая, что есть и другие ограничивающие факторы, которые означают, что вы хотите изменить как можно меньше внешних деталей о классе (примечание: я не проверял это, поэтому, возможно, придется немного подправить)

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 или аналогичного, но я пытаюсьg, чтобы держать прицел довольно плотно ...

0 голосов
/ 27 сентября 2019

Внедрение фабрики с использованием делегата 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>();

. Для модульного тестирования функция можетбыть легко созданным, чтобы обеспечить столько тестов, сколько нужно для теста

...