Enumerating a Mocked Indexer Property Causes the Collection to Become Empty

末鹿安然 提交于 2019-12-11 04:53:06

问题


Okay, to reproduce, here is what you need

public interface IWorkbookSet
{
    IWorkbooks Workbooks { get; }
}

public interface IWorkbooks : IEnumerable
{
    IWorkbook this[int index] { get; }
    IWorkbook this[string name] { get; }
    int Count { get; }
}

public interface IWorkbook
{
    IWorksheets Worksheets { get; }
}

public interface IWorksheets : IEnumerable
{
    IWorksheet this[int index] { get; }
    IWorksheet this[string name] { get; }
    int Count { get; }
    IWorksheet Add();
    IWorksheet AddAfter(IWorksheet sheet);
    IWorksheet AddBefore(IWorksheet sheet);
    bool Contains(IWorksheet worksheet);
}

public interface IWorksheet
{
    string Name { get; set; }
}

Set up a Microsoft unit test using the following code

[TestInitialize]
public void Initialize()
{
    List<string> fakeSheetNames = new List<string>()
    {
        "Master", "A", "B", "C", "__ParentA", "D", "wsgParentB", "E", "F", "__ParentC", "__ParentD", "G"
    };

    // Worksheets.
    var fakeWorksheetsList = new List<IWorksheet>();
    foreach (string name in fakeSheetNames)
    {
        var tmpMock = new Mock<IWorksheet>();
        tmpMock.Setup(p => p.Name).Returns(name);
        tmpMock.Setup(p => p.Visible)
             .Returns(parentPrefixes.Any(p => name.StartsWith(p)) ?
                  SheetVisibility.Hidden :
                  SheetVisibility.Visible);

        fakeWorksheetsList.Add(tmpMock.Object);
    }

    var mockWorksheets = new Mock<IWorksheets>();
    mockWorksheets.Setup(m => m[It.IsAny<int>()]).Returns<int>(index => fakeWorksheetsList[index]);
    mockWorksheets.Setup(m => m.GetEnumerator()).Returns(fakeWorksheetsList.GetEnumerator());
    mockWorksheets.SetupGet(m => m.Count).Returns(fakeWorksheetsList.Count);

    // Workbook.
    var mockWorkbook = new Mock<IWorkbook>();
    mockWorkbook.Setup(p => p.Name).Returns("Name");
    mockWorkbook.Setup(p => p.FullName).Returns("FullName");
    mockWorkbook.Setup(p => p.Worksheets).Returns(mockWorksheets.Object);

    // Workbooks.
    var fakeWorkbooksList = new List<IWorkbook>() { mockWorkbook.Object };

    var mockWorkbooks = new Mock<IWorkbooks>();
    mockWorkbooks.Setup(m => m[It.IsAny<int>()]).Returns<int>(index => fakeWorkbooksList[index]);
    mockWorkbooks.Setup(m => m.GetEnumerator()).Returns(fakeWorkbooksList.GetEnumerator());
    mockWorkbooks.SetupGet(m => m.Count).Returns(fakeWorkbooksList.Count);

    // WorkbookSet.
    mockWorkbookSet = new Mock<IWorkbookSet>();
    mockWorkbookSet.Setup(m => m.Workbooks).Returns(mockWorkbooks.Object);

    var expectedWorkBooksIndex = 0;
    var expectedWorkSheetIndex = 1;
    var expected = fakeWorksheetsList[expectedWorkSheetIndex];

    // Setup test.
    var workbookSet = mockWorkbookSet.Object;
    var actual = workbookSet
         .Workbooks[expectedWorkBooksIndex]
         .Worksheets[expectedWorkSheetIndex];

    Assert.AreEqual(expected, actual);
    Assert.AreEqual(12, workbookSet.Workbooks[0].Worksheets.Count);
}

Now in a test method, do this

[TestMethod]
public async Task StrucutreGenerationAsyncTest()
{
    foreach (IWorksheet ws in mockWorkbookSet.Object.Workbooks[0].Worksheets)
        Trace.WriteLine("1111 ws = " + ws.Name);

    foreach (IWorksheet ws in mockWorkbookSet.Object.Workbooks[0].Worksheets)
        Trace.WriteLine("2222 ws = " + ws.Name);
}

Output:

Test Name:  StrucutreGenerationAsyncTest
Test Outcome:   Passed
Result StandardOutput:  
Debug Trace:
1111 ws = Master
1111 ws = A
1111 ws = B
1111 ws = C
1111 ws = __ParentA
1111 ws = D
1111 ws = wsgParentB
1111 ws = E
1111 ws = F
1111 ws = __ParentC
1111 ws = __ParentD
1111 ws = G

The first foreach enumerate the IWorksheets, the second does not(?) as mockWorkbookSet.Object.Workbooks[0].Worksheets is now empty.

Even more odd is this

[TestMethod]
public async Task StrucutreGenerationAsyncTest()
{
    if (mockWorkbookSet.Object.Workbooks[0].Worksheets
            .Cast<IWorksheet>().Any(ws => ws.Name.Compare("Master")))
        Trace.WriteLine("Match!");

    foreach (IWorksheet ws in mockWorkbookSet.Object.Workbooks[0].Worksheets)
        Trace.WriteLine("1111 ws = " + ws.Name);

    foreach (IWorksheet ws in mockWorkbookSet.Object.Workbooks[0].Worksheets)
        Trace.WriteLine("2222 ws = " + ws.Name);
}

Output:

Test Name:  StrucutreGenerationAsyncTest
Test Outcome:   Passed
Result StandardOutput:  
Debug Trace:
Match!
1111 ws = A
1111 ws = B
1111 ws = C
1111 ws = __ParentA
1111 ws = D
1111 ws = wsgParentB
1111 ws = E
1111 ws = F
1111 ws = __ParentC
1111 ws = __ParentD
1111 ws = G

Where has "Master" gone? It is like the act of enumerating removes items from the collection. Why is this happening and how can I fix it?


Edit #1: I have tried mocking the enumerator using a method, as follows

var mockWorksheets = new Mock<IWorksheets>();
mockWorksheets.Setup(m => m[It.IsAny<int>()]).Returns<int>(index => fakeWorksheetsList[index]);
mockWorksheets.Setup(m => m.GetEnumerator()).Returns(fakeWorksheetsList.GetEnumerator());
mockWorksheets.SetupGet(m => m.Count).Returns(fakeWorksheetsList.Count);

With

private IEnumerator<IWorksheet> WorksheetList()
{
    foreach (string name in fakeSheetNames)
    {
        var mock = new Mock<IWorksheet>();
        mock.Setup(p => p.Name).Returns(name);
        mock.Setup(p => p.Visible)
             .Returns(parentPrefixes.Any(p => name.StartsWith(p)) ?
                  SheetVisibility.Hidden :
                  SheetVisibility.Visible);
        yield return mock.Object;
    }
}

This does not help.


回答1:


mockWorksheets.Setup(m => m.GetEnumerator()).Returns(fakeWorksheetsList.GetEnumerator());

returns the same enumerator instance every time which when used once will need to be reset (resulting in the appearance of an empty collection on any subsequent enumeration).

If you want a new enumerator on every call, then you need to pass Returns a lambda expression:

mockWorkSheets.Setup(m => m.GetEnumerator()).Returns(() => fakeWorksheetsList.GetEnumerator());

The lambda will get called every time GetEnumerator() is called. So now enumerating the mock multiple times should then work as expected.

Reference Moq First() Last() and GetEnumerator() wierdness



来源:https://stackoverflow.com/questions/39147032/enumerating-a-mocked-indexer-property-causes-the-collection-to-become-empty

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!