简体   繁体   中英

How to mock an IEnumerable<interface> type and pass it to constructor

I got a class which looks like below

public interface ILocationProvider
{
    bool IsRequiredLocation (string type);
}

public class MyClass : IMyInterface
{
    private readonly IEnumerable<ILocationProvider> _locationProvider;
    public MyClass (ILocationProvider[] locationProvider)
    {
        _locationProvider = locationProvider;
    }
    public ILocationProvider ProvideRequireLocationObject(string type)
    {
        ILocationProvider location = _locationProvider.FirstOrDefault(x => x.IsRequiredLocation(type));
        return location;
    }
}

Now I am trying to write some tests for it. But I stuck passing the Mock<IEnumerable<ITransitReportCountryFlowProvider>> to constructor. Below my test code

[TestClass]
public class TMyClassTest
{
    private Mock<IEnumerable<ILocationProvider>> _locationProvider = null;
    private IMyInterface _myClass = null;
    [TestInitialize]
    public void InitializeTest ()
    {
        _locationProvider = new Mock<IEnumerable<ILocationProvider>>();
    }
    [TestMethod]
    public void ProvideRequireLocationObject_Test1()
    {
        //Given: I have type as 'PMI'
        string type = "PMI";
        //When: I call MyClass object
        _myClass = new MyClass(_locationProvider.Object); //wrong actual argument as the formal argument is an array of ILocationProvider
        //_locationProvider.Setup(x => x.IsRequiredCountryFlow(It.IsAny<string>())).Returns(true); //how do I setup
        ILocationProvider result = _myClass.ProvideRequireLocationObject(type);
        //Then: I get a type of ILocationProvider in return
        Assert.IsTrue(result is ILocationProvider);
    }
}

Problem 1: The line _myClass = new MyClass(_locationProvider.Object) in above test class, as the constructor's formal argument is ILocationProvider[] so I cannot pass a mocking object of Mock<IEnumerable<ILocationProvider>>

Problem 2: If I change the line private readonly IEnumerable<ILocationProvider> _locationProvider; in above MyClass to private readonly ILocationProvider[] _locationProvider; I will not be able to mock it as because mock must be an interface or an abstract or non-sealed class.

Problem 3: How do I set up for _locationProvider.FirstOrDefault(x => x.IsRequiredLocation(type)); in my test method

Problem 4: How do I assert that my method ProvideRequireLocationObject is returning a type of ILocationProvider

First of all, you don't need to mock the collection. Collections (arrays or lists) are tested well enough to trust on their implementation. Since your constructor expects an array, you need to pass an array. And the simplest way to do that is to simply pass an array. There is no reason to mock this at all.

Changing the implementation details of the class you are testing (as suggested in problem 2) will not change anything on the testing surface. Unit tests should always be independent from the internal implementation details anyway.

How do I assert that my method ProvideRequireLocationObject is returning a type of ILocationProvider

You don't need to do that. The method has that return type, so the compiler will only accept an implementation where the method returns that type. You are guaranteed by the language that if there is a return value , then it's of the ILocationProvider type. So you actually just need to check for null .

Taking your implementation, below is a possible way to test this. Note that you don't actually need to mock this. You usually mock things when the actual implementation is too difficult to set up (eg has other dependencies) or when providing a testable implementation is too much work (eg an interface with lots of method but you only need one). In this case, I'm assuming that the ILocationProvider is easy to implement, so we're going to create a test type for this:

[TestClass]
public class MyClassTests
{
    [TestMethod]
    public void ProvideRequireLocationObject_EmptyCollection()
    {
        // arrange
        var providers = new ILocationProvider[] {};
        var obj = new MyClass(providers);

        // act
        var result = obj.ProvideRequireLocationObject();

        // assert
        Assert.IsNull(result);
    }

    [TestMethod]
    public void ProvideRequireLocationObject_NoRequiredLocation()
    {
        // arrange
        var providers = new ILocationProvider[] {
            new TestLocationProvider(false)
        };
        var obj = new MyClass(providers);

        // act
        var result = obj.ProvideRequireLocationObject();

        // assert
        Assert.IsNull(result);
    }

    [TestMethod]
    public void ProvideRequireLocationObject_OneRequiredLocation()
    {
        // arrange
        var providers = new ILocationProvider[] {
            new TestLocationProvider(true)
        };
        var obj = new MyClass(providers);

        // act
        var result = obj.ProvideRequireLocationObject();

        // assert
        Assert.AreEqual(providers[0], result);
    }

    [TestMethod]
    public void ProvideRequireLocationObject_OneRequiredLocationNotFirstInArray()
    {
        // arrange
        var providers = new ILocationProvider[] {
            new TestLocationProvider(false),
            new TestLocationProvider(true),
            new TestLocationProvider(false)
        };
        var obj = new MyClass(providers);

        // act
        var result = obj.ProvideRequireLocationObject();

        // assert
        Assert.AreEqual(providers[1], result);
    }

    [TestMethod]
    public void ProvideRequireLocationObject_MultipleRequiredLocations()
    {
        // arrange
        var providers = new ILocationProvider[] {
            new TestLocationProvider(true),
            new TestLocationProvider(true),
            new TestLocationProvider(true)
        };
        var obj = new MyClass(providers);

        // act
        var result = obj.ProvideRequireLocationObject();

        // assert
        Assert.AreEqual(providers[0], result);
    }


    public class TestLocationProvider : ILocationProvider
    {
        public TestLocationProvider(bool isRequiredLocation)
        {
            IsRequiredLocation = isRequiredLocation;
        }
        public bool IsRequiredLocation { get; private set; }
    }
}

Of course, you could expand those tests as necessary.

I believe are looking at it from the wrong angle. I think you don't need to mock the IEnumerable ( Mock<IEnumerable<ITransitReportCountryFlowProvider>> ) - IEnumerable has been testing front and back and besides you don't want to have to implement all its logic..

I think you should mock your own classes: Mock<ITransitReportCountryFlowProvider>

And pass a normal IEnumerable containing your mock in it

Something like:

[TestClass]
public class TMyClassTest
{
    private Mock<ILocationProvider> _locationProvider = null;
    private IEnumerable<ILocationProvider> _locationProviderCollection;
    private IMyInterface _myClass = null;
    [TestInitialize]
    public void InitializeTest ()
    {
        _locationProvider = new Mock<IEnumerable<ILocationProvider>>();
        _locationProviderCollection = new List<ILocationProvider> { _locationProvider };
    }
    [TestMethod]
    public void ProvideRequireLocationObject_Test1()
    {
        //Given: I have type as 'PMI'
        string type = "PMI";
        //When: I call MyClass object
        _myClass = new MyClass(_locationProviderCollection); //wrong actual argument as the formal argument is an array of ILocationProvider

        .....
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM