4

I want to know how I can mock an indexed property and there are many questions on this:

  1. Moq an indexed property and use the index value in the return/callback
  2. How to MOQ an Indexed property
  3. How to Moq Setting an Indexed property

et al. But in my case there is an added complexity. The indexed property is readonly. So, I need to be able to test a piece of code that does the following

if (workbook.Worksheets.Cast<IWorksheet>().Any(
        ws => ws.Name.CompareNoCase(Keywords.Master)))
{
    ...
}

where we have the following class structure

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

So in my test method, I have tried (and failed) to do this by overriding the GetEnumerator() method as this is exactly what Cast() calls; I do this as follows:

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

List<IWorksheet> worksheetMockList = new List<IWorksheet>();
foreach (string name in fakeSheetNames)
{
    Mock<IWorksheet> 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);

    worksheetMockList.Add(tmpMock.Object);
}

Mock<IWorkbook> mockWorkbook = new Mock<IWorkbook>();
mockWorkbook
    .Setup(p => p.Worksheets.GetEnumerator())
    .Returns(worksheetMockList.GetEnumerator());

// I can't do this as per the threads referenced above, as the property is read only.
//for (int i = 0; i < worksheetMockList.Count; ++i)
    //mockWorkbook.SetupGet(p => p.Worksheets[i] = worksheetMockList[i])...

How can I mock my workbook.Worksheets read only iterator property?


I have one more level of abstraction. I need to add the IWorkbook to an IWorkbooks collection (like we did for IWorksheets). I did not put this in the original question as it is merely doing the same thing as we did for IWorksheets, how ever it does not work. The interfaces are

public interface IWorkbookSet
{
    ...
    IWorkbooks Workbooks { get; }
}

and

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

So to attempt to handle this I am mocking in the following fashion following the great answer below. However, the loops below do not work as expected.

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


Mock<IWorkbook> mockWorkbook = new Mock<IWorkbook>();
List<IWorksheet> worksheetMockList = new List<IWorksheet>();
foreach (string name in fakeSheetNames)
{
    Mock<IWorksheet> tmpWorksheetMock = new Mock<IWorksheet>();
    tmpWorksheetMock.Setup(p => p.Name).Returns(name);
    tmpWorksheetMock.Setup(p => p.Visible)
        .Returns(parentPrefixes.Any(p => name.StartsWith(p)) ? 
            SheetVisibility.Hidden : 
            SheetVisibility.Visible);

    worksheetMockList.Add(tmpWorksheetMock.Object);
}
var mockWorksheets = new Mock<IWorksheets>();
mockWorksheets.Setup(m => m[It.IsAny<int>()]).Returns<int>(index => worksheetMockList[index]);
mockWorksheets.Setup(m => m.GetEnumerator()).Returns(worksheetMockList.GetEnumerator());

mockWorkbook
    .Setup(p => p.Worksheets)
    .Returns(mockWorksheets.Object);
mockWorkbook.Setup(p => p.Name).Returns("Name");
mockWorkbook.Setup(p => p.FullName).Returns("FullName");

// This works.
foreach (IWorksheet ws in mockWorkbook.Object.Worksheets)
    Trace.WriteLine(ws.Name);

mockWorkbookSet = new Mock<IWorkbookSet>();
var mockWorkbooks = new Mock<IWorkbooks>();
List<IWorkbook> workbookMockList = new List<IWorkbook>() { mockWorkbook.Object };

mockWorkbooks.Setup(m => m[It.IsAny<int>()]).Returns<int>(index => workbookMockList[index]);
mockWorkbooks.Setup(m => m.GetEnumerator()).Returns(workbookMockList.GetEnumerator());
mockWorkbookSet
    .Setup(p => p.Workbooks)
    .Returns(mockWorkbooks.Object);

// Count is zero here??
foreach (IWorkbook wb in mockWorkbookSet.Object.Workbooks)
    Trace.WriteLine(wb.Worksheets.Count);

Thanks very much.


Edit #2: Using your code I have some interesting behavior...

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

// This prints "A" - GOOD!
Trace.WriteLine("Actual " + actual.Name);

// This passes.
Assert.AreEqual(expected, actual);

// This works.
foreach (IWorksheet ws in mockWorkbook.Object.Worksheets)
    Trace.WriteLine(ws.Name);

// This works.
Trace.WriteLine(mockWorkbookSet.Object.Workbooks[0].Name);

// This does not write anything - WHY?
foreach (IWorksheet ws in mockWorkbookSet.Object.Workbooks[0].Worksheets.Cast<IWorksheet>())
    Trace.WriteLine(ws.Name);

// This fails.
foreach (IWorkbook workbook in workbookSet.Workbooks.Cast<IWorkbook>())
    Assert.IsTrue(workbook.Worksheets.Count > 0);
Community
  • 1
  • 1
MoonKnight
  • 23,214
  • 40
  • 145
  • 277

2 Answers2

3

by using

mockWorkSheets
    .Setup(m => m[It.IsAny<int>()])
    .Returns<int>(index => worksheetMockList[index]);

where It.IsAny<int>() and .Returns<int>(index => ...) gives access to the value passed into the mock You can access the index in the .Returns method.

The following example shows how to setup the mocks

[TestMethod]
public void Mock_Readonly_Indexer_Property() {
    //Arrange
    var parentPrefixes = new List<string>() { "__", "wsg" };
    var 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 = Mock.Of<IWorksheet>();
        tmpMock.Name = name;
        tmpMock.Visible = parentPrefixes.Any(p => name.StartsWith(p)) ?
                SheetVisibility.Hidden :
                SheetVisibility.Visible;

        fakeWorkSheetsList.Add(tmpMock);
    }

    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());
    //Assuming a Count property exists
    mockWorkSheets.Setup(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.Setup(m => m.Count).Returns(fakeWorkbooksList.Count);

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

    var workbookSet = mockWorkbookSet.Object;

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

    //Act
    var actual = workbookSet
        .Workbooks[expectedWorkBooksIndex]
        .Worksheets[expectedWorkSheetIndex];

    //Assert
    Assert.AreEqual(expected, actual);

    foreach (IWorkbook workbook in workbookSet.Workbooks) {
        Assert.IsTrue(workbook.Worksheets.Count > 0);
    }

}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thank you very much for your help here but I am still having issues with this. Can you check the edit? I will award a bounty for your help in due course... – MoonKnight Aug 25 '16 at 10:46
  • @Killercam, You have bits and pieces of the same overall problem spread among different questions. I had asked for some information on one of the other questions to help clarify issue so I could reproduce your problem and provide a full solution but barring that I've just shown the examples of how to fix the specific issues you asked about. You could then piece the responses together to solve your overall problem – Nkosi Aug 25 '16 at 10:50
  • Yeah sorry about that. I realized half way through the other thread that I had asked about the problem in a manner that was not concise enough. I hadn't really grasped the problem (of read only indexer properties). I had not done this for any other reason that clarity. Anyway, i got ahead of myself; the edit is not available. Thanks again for all your time, it is most appreciated. – MoonKnight Aug 25 '16 at 10:53
  • Have you any idea on the edit and why I can enumerate the `IWorkbooks` collection? – MoonKnight Aug 25 '16 at 11:16
  • As an aside. when I had originally asked for a complete example, it was to avoid all the back a forth. If a complete picture of what you were trying to test was know then a full solution could have been provided quickly. I'm now piecing together all the parts to try and solve your problem, but it is a lot of guessing on my part as the SUT is unknown to me. I could be mocking stuff up that don't need to be mocked but that's based on the current information i have – Nkosi Aug 25 '16 at 11:23
  • The complete picture is as above with the edit. The issue is posting massively long questions containing mountains of code. There was (and is) no reason to belive that the same mocking process for the `IWorksheets` would not work for the `IWorkbooks` as they are identical constructs. Hence why I cut it down... – MoonKnight Aug 25 '16 at 11:29
  • @Killercam, updated example to include `Workbooks` and `WorkbookSet` and it passes when test. – Nkosi Aug 25 '16 at 11:53
  • Thanks, but `foreach (IWorksheet ws in mockWorkbook.Object.Worksheets)` works. `mockWorkbookSet.Object.Workbooks[0].Name` we get the correct value. BUT, `foreach (IWorkbook wb in mockWorkbookSet.Object.Workbooks[0].Worksheets)` does _not_ work, there are no worksheets. – MoonKnight Aug 25 '16 at 12:16
  • The Assertion in the `foreach` loop you have just added fails for me. Does this pass for you? – MoonKnight Aug 25 '16 at 12:19
  • Sorry that was a typo. When `IWorksheet` is used we get the same - no items in the collection. You assertion with `Worksheets > 0` also fails for me... – MoonKnight Aug 25 '16 at 12:22
  • Another interesting thing, if I enumerate the `Worksheets` with `foreach (IWorksheet worksheet in workbookSet.Workbooks[0].Worksheets)` twice, the first time the collection is there, the second time its gone!? – MoonKnight Aug 25 '16 at 13:21
  • It's nuts. I am going to post another question as I have never come across this before and it does not seem to be addressed anywhere... I will let you know when it is posted... – MoonKnight Aug 25 '16 at 13:41
  • The new question based on this is http://stackoverflow.com/questions/39147032/enumerating-a-mocked-indexer-property-causes-the-collection-to-become-empty – MoonKnight Aug 25 '16 at 13:43
2

Perhaps you're looking for this?

mockWorkbook.SetupGet(wb => wb.Worksheets[i]).Returns(() => worksheetMockList[i]);
hiho
  • 81
  • 1
  • 3