如何模拟 IEnumerable<interface> 类型并将其传递给构造函数

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

我得到了一个 class,如下所示

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

现在我正在尝试为它编写一些测试。但是我坚持将 Mock<IEnumerable<ITransitReportCountryFlowProvider>> 传递给构造函数。下面是我的测试代码

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

问题1:上面测试class中的行_myClass = new MyClass(_locationProvider.Object),因为构造函数的形式参数是ILocationProvider[]所以我不能通过一个Mock<IEnumerable<ILocationProvider>>

的模拟对象

问题 2: 如果我将上面 MyClass 中的行 private readonly IEnumerable<ILocationProvider> _locationProvider; 更改为 private readonly ILocationProvider[] _locationProvider; 我将无法将其模拟为因为 mock 必须是接口或抽象或非密封 class.

问题3:如何在我的测试方法

中设置_locationProvider.FirstOrDefault(x => x.IsRequiredLocation(type));

问题 4: 如何断言我的方法 ProvideRequireLocationObject 正在返回 ILocationProvider

的类型

我相信是从错误的角度来看它。我认为你不需要模拟 IEnumerable (Mock<IEnumerable<ITransitReportCountryFlowProvider>>) - IEnumerable 一直在前后测试,此外你不想实现它的所有逻辑..

我认为你应该嘲笑你自己的类:Mock<ITransitReportCountryFlowProvider>

并传递包含您的模拟的正常 IEnumerable

类似于:

[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

        .....
    }
}

首先,你不需要模拟集合。集合(数组或列表)经过充分测试以信任它们的实现。由于您的构造函数需要一个数组,因此您需要 传递一个数组。最简单的方法就是简单地传递一个数组。根本没有理由嘲笑这个。

更改您正在测试的 class 的 实现细节 (如问题 2 中所建议的)不会改变测试表面上的任何内容。无论如何,单元测试应该始终独立于内部实现细节。

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

你不需要那样做。该方法具有 return 类型,因此编译器将只接受方法 return 类型的实现。语言保证 如果有 return 值 ,那么它是 ILocationProvider 类型。所以你实际上只需要检查 null.

根据您的实施,下面是一种可能的测试方法。请注意,您实际上不需要模拟它。当实际实现太难设置时(例如具有其他依赖项)或提供可测试的实现工作量太大(例如具有很多方法但您只需要一个方法的接口)时,您通常会模拟事物。在这种情况下,我假设 ILocationProvider 很容易实现,所以我们要为此创建一个测试类型:

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

当然,您可以根据需要扩展这些测试。