While refactoring a project I ran into an issue where consumers of an IEnumerable<T> method assumed that the returned enumeration could be enumerated many times. This is no longer true, the application throws NotSupportedException: "Only a single enumeration is supported by this IEnumerable."). There are many unit tests (using XUnit and Moq), but they did not catch this because they would use List<T> as the IEnumerable implementation. Now I want to make the unit tests expose this error.
I came up with the following implementation, which seems to work the way I want.
My questions:
- Is there some easier way of doing this in general (c#, regardless of Moq)?
- Is there some easier, built-in way to do this in Moq specifically?
public class DataTransferObject
{
public string Title { get; set; }
}
public interface IDemoClient
{
IEnumerable<DataTransferObject> GetData();
}
public class OnceEnumerator<T> : IEnumerator<T>
{
private IList<T> _data;
private int position = -1;
internal OnceEnumerator(IList<T> data)
{
_data = data;
}
public T Current => _data[position];
object IEnumerator.Current => _data[position];
public void Dispose()
{
}
public bool MoveNext()
{
position++;
return (position < _data.Count);
}
public void Reset()
{
throw new NotSupportedException("Reset(): Only a single enumeration is supported by this IEnumerable.");
}
}
public class OnceEnumerable<T> : IEnumerable<T>
{
private OnceEnumerator<T> _enum;
private bool _enumReturned = false;
public OnceEnumerable(IList<T> data)
{
_enum = new OnceEnumerator<T>(data);
}
public IEnumerator<T> GetEnumerator()
{
if (_enumReturned)
{
throw new NotSupportedException("GetEnumerator(): Only a single enumeration is supported by this IEnumerable.");
}
else
{
_enumReturned = true;
return _enum;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
}
public class UnitTest1
{
[Fact]
public void Test1()
{
var client = new Mock<IDemoClient>();
client.Setup(c => c.GetData()).Returns(new OnceEnumerable<DataTransferObject>(
new List<DataTransferObject> {
new DataTransferObject { Title = "foo" },
new DataTransferObject { Title = "bar" }
}));
var data = client.Object.GetData();
// enumerate once...
if (data.Any(x => x.Title == "biff"))
{
Console.WriteLine("found biff");
}
// I get the behaviour I want:
// System.NotSupportedException : GetEnumerator(): Only a single enumeration is supported by this IEnumerable.
if (data.Any(x => x.Title == "boff"))
{
Console.WriteLine("found boff");
}
}
}