Killercam Killercam - 3 months ago 95
C# Question

Mock a readonly Indexer Property

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

Answer

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 = 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());
    //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);
    }

}