Mihaimyh
Mihaimyh

Reputation: 1410

xUnit Mock request new instance from IoC container

I am writing some unit tests for a method that uploads a file using SSH.Net.

The project is a WPF app and uses Caliburn.Micro as MVVM framework and also to inject the following object in the constructor of the class I am testing:

    private IFileTransferManager _fileTransferManager;

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

In the test project I am mocking IFileTransferManager:

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

But now I got to the point, when in code I need to ask for a new instance of IFileTransferManager from IoC container, IoC being a static class in Caliburn.Micro:

    _fileTransferManager = IoC.Get<IFileTransferManager>();

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

How can I refactor the above code to make it testable, because currently it throws System.InvalidOperationException in Caliburn.Micro.dll due to the fact that I am re-instantiating _fileTransferManager?

Upvotes: 0

Views: 826

Answers (2)

GPW
GPW

Reputation: 2626

I would probably do something like this, assuming there are other limiting factors that mean you want to change as little outward detail about the class as possible (note: I haven't tested this so may have to tweak a little)

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;
    }
}

Then in your testing:

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

I suggest this approach because:

  • It provides a way of overriding the way the class gets the IFileTransferManager for testing
  • It 'falls back' to the default implementation if this override is not used, preserving the original behaviour - you don't need to change existing calls to this class at all from non-testing code
  • It does not change the Constructor or add a new one, which I assume is a problem since you don't simply inject an instance of the IFileTransferManager in.

one improvement might be to make the set internal which would prevent other projects from setting this method, and it could then be exposed via InternalVisibleTo or similar, but I'm trying to keep the scope fairly tight...

Upvotes: 1

Nkosi
Nkosi

Reputation: 247423

Inject a factory using a Func<TResult> delegate.

private readonly Func<IFileTransferManager> fileTransferManagerFactory;

public FileUploadViewModel(Func<IFileTransferManager> fileTransferManagerFactory) : base(eventAggregator) {
    this.fileTransferManagerFactory = fileTransferManagerFactory;
}

This would allow for as many instances as needed being created when uploading

//get an instance using factory delegate
var fileTransferManager =  fileTransferManagerFactory();

await fileTransferManager.UploadFile(connection, file.FullName, destinationPath).ConfigureAwait(false); IoC.Get<IFileTransferManager>();

For unit testing a function can be easily created to provid as many mocks needed for the test case

Upvotes: 0

Related Questions