这比实际问题更能解决/解决问题。我在这里发帖,因为我在堆栈溢出时找不到这个解决方案,或者确实经过很多谷歌搜索。
问题:
我首先使用EF 4代码创建一个MVC 3 webapp,我想编写单元测试。我也在使用NCrunch在运行代码时运行单元测试,所以我想避免在这里支持实际的数据库。
其他解决方案:
IDataContext
我发现这是创建内存datacontext的最常用方法。它实际上涉及为MyDataContext编写接口IMyDataContext,然后在所有控制器中使用该接口。这样做的一个例子是here。
这是我最初使用的路线,我甚至编写了一个T4模板来从MyDataContext中提取IMyDataContext,因为我不喜欢维护重复的相关代码。
但是我很快发现在使用IMyDataContext而不是MyDataContext时,一些Linq语句在生产中失败。特别是像这样的查询抛出一个NotSupportedException
var siteList = from iSite in MyDataContext.Sites
let iMaxPageImpression = (from iPage in MyDataContext.Pages where iSite.SiteId == iPage.SiteId select iPage.AvgMonthlyImpressions).Max()
select new { Site = iSite, MaxImpressions = iMaxPageImpression };
我的解决方案
这其实很简单。我只是创建一个MyInMemoryDataContext子类到MyDataContext并覆盖所有IDbSet< ..>属性如下:
public class InMemoryDataContext : MyDataContext, IObjectContextAdapter
{
/// <summary>Whether SaveChanges() was called on the DataContext</summary>
public bool SaveChangesWasCalled { get; private set; }
public InMemoryDataContext()
{
InitializeDataContextProperties();
SaveChangesWasCalled = false;
}
/// <summary>
/// Initialize all MyDataContext properties with appropriate container types
/// </summary>
private void InitializeDataContextProperties()
{
Type myType = GetType().BaseType; // We have to do this since private Property.Set methods are not accessible through GetType()
// ** Initialize all IDbSet<T> properties with CollectionDbSet<T> instances
var DbSets = myType.GetProperties().Where(x => x.PropertyType.IsGenericType && x.PropertyType.GetGenericTypeDefinition() == typeof(IDbSet<>)).ToList();
foreach (var iDbSetProperty in DbSets)
{
var concreteCollectionType = typeof(CollectionDbSet<>).MakeGenericType(iDbSetProperty.PropertyType.GetGenericArguments());
var collectionInstance = Activator.CreateInstance(concreteCollectionType);
iDbSetProperty.SetValue(this, collectionInstance,null);
}
}
ObjectContext IObjectContextAdapter.ObjectContext
{
get { return null; }
}
public override int SaveChanges()
{
SaveChangesWasCalled = true;
return -1;
}
}
在这种情况下,我的CollectionDbSet&lt;&gt;是FakeDbSet的略微修改版本&lt;&gt; here(它只使用底层的ObservableCollection和ObservableCollection.AsQueryable()实现IDbSet。)
此解决方案适用于我的所有单元测试,特别是NCrunch即时运行这些测试。
完整集成测试
这些单元测试测试所有业务逻辑,但一个主要缺点是您的LINQ语句都不能保证与您的实际MyDataContext一起使用。这是因为针对内存数据上下文的测试意味着您正在替换Linq-To-Entity提供程序,而不是Linq-To-Objects提供程序(正如在this SO问题的答案中所指出的那样)。< / p>
要解决这个问题,我在单元测试中使用Ninject并在我的单元测试中设置InMemoryDataContext来绑定而不是MyDataContext。然后,您可以在运行集成测试时使用Ninject绑定到实际的MyDataContext(通过app.config中的设置)。
if(Global.RunIntegrationTest)
DependencyInjector.Bind<MyDataContext>().To<MyDataContext>().InSingletonScope();
else
DependencyInjector.Bind<MyDataContext>().To<InMemoryDataContext>().InSingletonScope();
如果您对此有任何反馈,请告诉我,但总会有所改进。
答案 0 :(得分:3)
根据我在问题中的评论,这更有助于其他人在SO上搜索此问题。但正如问题中的评论所指出的那样,还有很多其他设计方法可以解决这个问题。