Mocking collections with Rhino Mocks

2019-07-18 09:22发布

So this is something I guess many people want to do, mock a collection. In the past with Rhino I have done this with something like:

var col_mock = MockRepository.GenerateMock<ICustomCollection>(); // returns ICustom let's say
List<ICustom> col_real = new List<ICustom>();
col_real.Add(custom_mock1);
col_real.Add(custom_mock2);
col_real.Add(custom_mock3);
col_mock.Stub(x => x.GetEnumerator()).Return(col_real.GetEnumerator());

So yup this works fine, when you foreach col_mock you get the mocked (custom_mock1 etc.) objects back. Great! We have successfully mocked a custom collection by using a typed list to actually store a load of mocked objects.

The problem is, you can only do this once! you can only foreach this collection once. Does anyone know (without creating an actual custom collection...) how I can achieve the mocking of a custom collection which can be iterated more than once?

2条回答
\"骚年 ilove
2楼-- · 2019-07-18 09:43

So this is something I guess many people want to do, mock a collection.

Actually, no. The point of a mock is to quickly conjure up an object with a specific interface and behavior that you have full control over, so that you can exercise your class under test.

But in the case where you need an ICollection<Foo>, you can typically just create a List<Foo> instance. You can populate a list in your unit test any way you want; you already have full control.

Or suppose you need a IEnumerable<Foo> with more interesting behavior that you can't produce with a list, like an infinite enumerable or an enumerable that throws exceptions. This can be done by defining an iterator method in your unit test class with the yield keyword. Again, a mock is not necessary.

查看更多
Emotional °昔
3楼-- · 2019-07-18 09:48

The problem is that the enumerator is only instantiated once, when you call Return. Then it returns the same instance which is already at the end of the list after the first foreach.

You need to instantiate a new enumerator each time when GetEnumerator is called. You could use WhenCalled to do so.

Return is still needed, because Rhino Mocks will complain when it is missing. But it doesn't matter what you pass as argument.

[TestMethod]
public void GetEnumerator()
{
    IList<int> col_mock = MockRepository.GenerateMock<IList<int>>();
    List<int> col_real = new List<int>();
    col_real.Add(1);
    col_real.Add(2);
    col_real.Add(3);
    col_mock
        .Stub(x => x.GetEnumerator())
        // create new enumerator instance for each call
        .WhenCalled(call => call.ReturnValue = col_real.GetEnumerator())
        .Return(null) // is ignored, but needed for Rhinos validation
        .Repeat.Any();

    foreach (int i in col_mock)
    {
    }

    int count = 0;
    foreach (int i in col_mock)
    {
        count++;
    }
    Assert.AreSame(3, count);
}
查看更多
登录 后发表回答