Search code examples
c#unit-testingmoqmockdbset

Enumerating a Moq'ed IDbSet throws exception: "Collection was modified; enumeration operation may not execute."


I'm trying to Mock IDbSet using the Moq framework. The unit test should add a new record (entity) to an existing Mocked DbSet collection (SetUp) and return the count of the new collection.

My TestInitialize Setup looks like:

public class BlogTests
    {
        private IRepository _repository;

        [TestInitialize]
                public void Setup()
                {
                    var blogEntries = new List<BlogEntry>
                    {
                        new BlogEntry()
                        {
                            //...init the object
                        }
                    };
                    var queryableBlogEntries = QueryableDbSetMock.GetQueryableMockDbSet<BlogEntry>(blogEntries);
                    var repoMock = new Mock<IRepository>();
                    repoMock.Setup(x => x.BlogEntries).Returns(queryableBlogEntries);
                    _repository = repoMock.Object;
                }

And here is the method which returns a mock IDbSet, given a List:

public class QueryableDbSetMock
    {
        public static IDbSet<T> GetQueryableMockDbSet<T>(List<T> sourceList) where T : class
        {
            var queryable = sourceList.AsQueryable();

            var dbSet = new Mock<IDbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
            dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
            dbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(s => sourceList.Add(s)); 
            return dbSet.Object;
        }
    }

In the unit test I would arrange a new BlogEntry object and try to add it to the mocked IDbSet, after which I would expect the total count of the collection to be two elements/records.

The following Unit Test succeeds:

public void IndexTest()
        {
            //Arrange
            var entries = _repository.BlogEntries;

            var newEntry = new BlogEntry()
            {
                //...init the second object
            };

            //Act
            entries.Add(newEntry);
            var count = entries.Count();

            //Assert
            Assert.AreEqual(2, count);
        }

But when I try to do the counting using the GetEnumerator method, the Enumerator.MoveNext command will throw the exception:

'System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' '.

Here is the code of the altered Unit Test:

[TestMethod()]
        public void IndexTest2()
        {
            //Arrange
            var entries = _repository.BlogEntries;

            var newEntry = new BlogEntry()
            {
                //...init the second object
            };

            //Act
            entries.Add(newEntry);
            var enumerator = entries.GetEnumerator();
            int count = 0;
            while (enumerator.MoveNext()) //Throws Exception
            {
                count++;
            }
            //Assert
            Assert.AreEqual(2, count);
        }

So my confusion lies in the fact that the first Unit Test seems to have no problems in calculating the count of the altered collection, but when trying to do the counting kind of the hard way, the Test fails.

Any clarification on this would be highly appreciated!


Solution

  • The iterator is not resetting as the same instance is being used for all the calls due to how moq was setup

    .Returns(queryable.GetEnumerator());
    

    returns the same enumerator instance every time which when used once will need to be reset (resulting in the exception after modifying the collection).

    If you want a new enumerator on every call, then you need to pass Returns a lambda expression:

    Update the setup of the GetEnumerator method to

    .Returns(() => queryable.GetEnumerator()); //<-- note the function
    

    The lambda will get called every time GetEnumerator() is called. So now enumerating the mock multiple times should then work as expected.

    This would allow for multiple passes at the enumerator as the initial setup would return the same enumerator for each call. Since the enumerator was not reset previously then when you tried to traverse it again you encountered the error.