6

我们的编程涉及一些使用内存中数据的模拟测试。因此,我们实现了以下代码,首先创建 Customer 对象的 In-Memory Data

        // Let us create some in-memory data
        // Create a list of Customer
        List<Customer> listOfCustomers =  new List<BlahBlahExample.Domain.Objects.Customer>()
                                   { new Customer { CustomerID = "1    ",Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Chicago Bulls", ContactName = "Michael Jordan", ContactTitle = "top basket ball player", Address = "332 testing lane", City = "Chicago", Region = "Illinois", PostalCode = "484894", Country = "USA", Phone = "3293993", Fax = "39393" },
                                     new Customer { CustomerID = "2    ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Miami Heat", ContactName = "Lebron James", ContactTitle = "second best basket ball player", Address = "90 test street", City = "Miami", Region = "Florida", PostalCode = "4869394", Country = "USA", Phone = "3293213", Fax = "33393" },
                                     new Customer { CustomerID = "3    ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Oklahoma City Thunder", ContactName = "Kevin Durant", ContactTitle = "current top basket ball player", Address = "35 test row", City = "Oklahoma City", Region = "Oklahoma", PostalCode = "480290", Country = "USA", Phone = "304923", Fax = "33325" }
                                   };


        // Convert the list to an IQueryable list
        IQueryable<Customer> queryableListOfCustomerInMemoryData = listOfCustomers.AsQueryable();



        // Let us create a Mocked DbSet object.
        Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>> mockDbSet = new Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>>();

        // Force DbSet to return the IQueryable members
        // of our converted list object as its 
        // data source
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Provider).Returns(queryableListOfCustomerInMemoryData.Provider);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Expression).Returns(queryableListOfCustomerInMemoryData.Expression);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.ElementType).Returns(queryableListOfCustomerInMemoryData.ElementType);
        mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.GetEnumerator()).Returns(queryableListOfCustomerInMemoryData.GetEnumerator());
        mockDbSet.Setup(m => m.Add(It.IsAny<Customer>())).Callback<Customer>(listOfCustomers.Add);


        Mock<BlahBlahAuditMappingProvider> jsAudtMppngPrvdr = new Mock<BlahBlahAuditMappingProvider>();
        Mock<BlahBlahDataContext> fctry = new Mock<BlahBlahDataContext>(jsAudtMppngPrvdr.Object);
        Mock<BlahBlahDataContext> qryCtxt = new Mock<BlahBlahDataContext>();
        Mock<BlahBlahAuditContext> audtCtxt = new Mock<BlahBlahAuditContext>();


         Mock<BlahBlahDataContext> mockedReptryCtxt = new Mock<BlahBlahDataContext>();


         mockedReptryCtxt.Setup(q => q.Customers).Returns(mockDbSet.Object);


        mockedReptryCtxt.Setup(q => q.Set<Customer>()).Returns(mockDbSet.Object);


        mockedReptryCtxt.CallBase = true;


        DbSet<Customer> inMemoryDbSetCustomer = mockedReptryCtxt.Object.Set<Customer>();

在下一个代码摘录中(这是我们的“测试中的代码”),我将一个新客户添加到现有的内存数据中,然后在模拟对象上调用 SaveChanges。

                    Customer returnCust = (Customer)(mockedReptryCtxt.Object.Set<Customer>().Add(new Customer { CustomerID = "4    ", Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Kolkota Knights", ContactName = "Sachin Tendulkar", ContactTitle = "current top cricket player", Address = "35 test row", City = "Kolkota", Region = "West Bengal", PostalCode = "3454534", Country = "India", Phone = "304923", Fax = "33325" }));

            mockedReptryCtxt.Object.SaveChanges();

稍后在代码中,我有以下代码摘录,其中 _context.Set() 将返回我们之前创建的 In-Memory Data DBSet

        var query = _context.Set<TEntity>().AsQueryable();

        if (typeof(TEntity).Name.Contains("Audit"))
        {
            return query;
        }

        if (includes != null && includes.Any())
        {
            foreach (var include in includes)
            {
                query = query.Include(include);
            }
        }


        List<TEntity> resultsAsList = query.ToList(); // Error Thrown When using ToList()

       var results = resultsAsList.AsQueryable();

当我们调用 ToList() 时,它会抛出以下错误:

System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=Collection was modified; enumeration operation may not execute.
  Source=mscorlib
  StackTrace:
       at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
       at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
       at System.Collections.Generic.List`1.Enumerator.MoveNext()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
        at BlahBlah.Framework.EntityFramework.EntityFrameworkRepository`1.ConcreteQuery(List`1      includes) in d:\EMIS\BlahBlah       Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.EntityFramework\EntityFrameworkRepository.c     s:line 51
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery_callback(List`1 includes)
        at Castle.Proxies.Invocations.EntityFrameworkRepository`1_ConcreteQuery.InvokeMethodOnTarget()
       at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
        at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx,     CurrentInterceptContext localctx)
        at Moq.Interceptor.Intercept(ICallContext invocation)
        at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
        at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery(List`1 includes)
         at BlahBlah.Framework.Core.Repository.BaseRepository`1.Query(List`1 includes) in      d:\EMIS\BlahBlah      Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.Core\Repository\BaseRepository.cs:line 149
         at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query_callback(List`1 includes)
         at Castle.Proxies.Invocations.IRepository`1_Query.InvokeMethodOnTarget()
         at Castle.DynamicProxy.AbstractInvocation.Proceed()
          at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
        at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx,      CurrentInterceptContext localctx)
         at Moq.Interceptor.Intercept(ICallContext invocation)
          at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
         at Castle.DynamicProxy.AbstractInvocation.Proceed()
        at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query(List`1 includes)
         at      BlahBlah.Test.Unit.CntrlrsTests.CustomerControllerTest.Test_Creation_Of_Customer_Using_Constructor_Of     _Customer_Controller_That_Expects_Arguments() in d:\EMIS\BlahBlah      Framework\BlahBlahFrameworkLightweight\BlahBlah.Test.Unit\CntrlrsTests\CustomerControllerTest.cs:line       278
    InnerException: 

我们需要采取哪些步骤来阻止上述错误被抛出(最好不要更改太多我们的被测代码)?

4

2 回答 2

11

我也遇到了这个问题,但不迭代集合对我来说并不是一个真正的选择。经过一番思考,我确实想出了一个解决方案。问题在于,模拟从原始列表中的固定 IQueryable 对象设置了各种 IQueryable 属性。这会导致对该列表的任何修改都会使相应的 IQueryable 无效。解决方案是使用带有 Moq 的 lambda 在每次访问时获得一个新的 IQueryable。

这是我使用所描述的技术创建的帮助函数,以使模拟 DBSet 更容易。

public static Mock<DbSet<T>> MockDbSet<T>(List<T> list) where T : class
{
    var mockSet = new Mock<DbSet<T>>();
    mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(() => list.AsQueryable().Provider);
    mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(() => list.AsQueryable().Expression);
    mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(() => list.AsQueryable().ElementType);
    mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => list.GetEnumerator());
    mockSet.Setup(m => m.Add(It.IsAny<T>())).Callback((T x) => list.Add(x));
    mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.AddRange(x));
    mockSet.Setup(m => m.Remove(It.IsAny<T>())).Callback((T x) => list.Remove(x));
    mockSet.Setup(m => m.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.RemoveAll(x.Contains));
    return mockSet;
}

编辑:添加了 AddRange、Remove、RemoveRange,因为为什么不...

编辑 2:修正 RemoveRange

于 2015-04-13T02:04:26.153 回答
1

我找到了一个非常笨拙的解决方案:

        List<TEntity> tempList = new List<TEntity>();

        for (int i = query.Count() - 1; i >= 0; i--)
        {
            tempList.Add(query.ElementAt(i));
        }

        List<TEntity> resultsAsList = tempList.ToList();

       var results = resultsAsList.AsQueryable();

在上述代码中,重要的是使用带有索引的 for 循环来遍历 DBSet 实例。此外,在循环中,您将每个元素添加到列表中。(基本上,避免使用迭代器很重要)

于 2014-11-19T06:22:11.407 回答