简体   繁体   中英

Mocking methods that return IEnumerable types

I am running into this issue in a somewhat larger context, but when condensed to the bare minimum, the question that remains is why does Moq not return the type specified in the interface for mocked methods? IEnumerable and List come back as empty arrays, When I specifically call Setup with a return value, it changes, at least in the example below.

    public interface ITestTarget    {
        public IEnumerable<int> getSomeData();
    }

    public class TestTarget : ITestTarget    {
        public List<int> getSomeData() {return new List<int>() { 1 };  }
    }

    public class MockTest     {
        [Fact]
        public void TestMe()
        {
            var testMock = new Mock<ITestTarget>();
            var someResult = testMock.Object.getSomeData();

            Assert.IsType<Int32[]>(someResult);    // Why does this pass? someResult is an Integer array!

            testMock.Setup(m => m.getSomeData()).Returns(new List<int> { });   // forcing it to a list

            someResult = testMock.Object.getSomeData();

            Assert.IsType<List<int>>(someResult);    // Now it is a list
        }
    }

My real issue is that Moq seems to return an empty array even when I specifically set it up with an empty List (of a complex type), but I will ask that question later.

Update and Re-phrasing my question

I had not considered the fact that an array IS an IEnumerable, my Interface has IEnumerable, and that therefore what Moq returns is not wrong. When I change the interface to List, the Mock returns a List.

I am not ready to change the return types in my code. I can custom-tweak my assertions to deal with either type, but:

I am writing unit tests for an API with many endpoints that do similar things. I am trying to generizie my assertion code in a base class, and I just don't always know if I am dealing with an array or a List.

How can I resolve this? Should I avoid using IEnumerable as return types altogether?

This was answered in the comments, but I hate to have this question float around without a formal answer, so allow me to recap:

public interface ITestTarget    {
    public ⚠️IEnumerable<int>⚠️ getSomeData();
}

public class TestTarget : ITestTarget     {
    public ⚠️List<int>⚠️ getSomeData() {return new List<int>() { 1 };  }
}

var testMock = new Mock<ITestTarget>();
var someResult = testMock.Object.getSomeData();

// someResult can be any type that implements IEnumerable. We cannot make
// any further assumptions. My code assumed a List.

What matters to the Mock is the type in the Interface, not the implementation. Thus, if no specific return type is given through a setup, you cannot make an assumption about the type that the Mock returns. In my case, the defaut was an array. This made code invalid that attempted to access the result as a List (not in my example here).

Casting it to an IEnumerable<T> and using ToList() , as suggested by @Guru Stron, resolves the type ambiguity and made it work for either case - a Setup that explicitely returns a List<T> or the Mock default that returns an empty <T>[] .

This really had nothing to do with Moq or unit testing but with using types and interfaces correctly. Now that I understand that, I can illustrate my misunderstanding differently:

    int[] a = {1,2,3};

    List<int> d = (List<int>)a;     // can't do that!
    
    List<int> c = (a as IEnumerable<int>).ToList();     // do this!

Now you may say: There are a lot of words in this post (and bandwidth needed to download them) to come to a rather trivial conclusion. I am going to downvote the question and this answer, but let me ask you this: did you know that Moq returns an array type for IEnumerables if no setup is given with another type?

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