枚举模拟索引器 属性 导致集合变空

Enumerating a Mocked Indexer Property Causes the Collection to Become Empty

好的,要重现,这就是你需要的

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

使用以下代码设置 Microsoft 单元测试

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

现在在测试方法中,执行此操作

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

输出:

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

第一个 foreach 枚举 IWorksheets,第二个 (?) 因为 mockWorkbookSet.Object.Workbooks[0].Worksheets 现在是空的。

更奇怪的是

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

输出:

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

"Master"去哪儿了?这就像枚举从集合中删除项目的行为。为什么会发生这种情况,我该如何解决?


编辑 #1:我尝试使用如下方法模拟枚举器

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

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

这没有帮助。

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

returns 相同的枚举器实例,每次使用一次都需要重置(导致在任何后续枚举中出现空集合)。

如果你想在每次调用时都使用一个新的枚举器,那么你需要传递 Returns 一个 lambda 表达式:

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

每次调用 GetEnumerator() 时都会调用 lambda。所以现在多次枚举模拟应该会按预期工作。

引用Moq First() Last() and GetEnumerator() wierdness