用EF4“Code First”和Repository进行unit testing
我试图得到一个unit testing一个非常简单的ASP.NET MVCtesting应用程序,我已经在最新的EF4 CTP中使用代码优先的方法build立一个句柄。 我对unit testing/嘲笑等方面并不是很有经验
这是我的存储库类:
public class WeightTrackerRepository { public WeightTrackerRepository() { _context = new WeightTrackerContext(); } public WeightTrackerRepository(IWeightTrackerContext context) { _context = context; } IWeightTrackerContext _context; public List<WeightEntry> GetAllWeightEntries() { return _context.WeightEntries.ToList(); } public WeightEntry AddWeightEntry(WeightEntry entry) { _context.WeightEntries.Add(entry); _context.SaveChanges(); return entry; } }
这是IWeightTrackerContext
public interface IWeightTrackerContext { DbSet<WeightEntry> WeightEntries { get; set; } int SaveChanges(); }
…及其实现WeightTrackerContext
public class WeightTrackerContext : DbContext, IWeightTrackerContext { public DbSet<WeightEntry> WeightEntries { get; set; } }
在我的testing中,我有以下几点:
[TestMethod] public void Get_All_Weight_Entries_Returns_All_Weight_Entries() { // Arrange WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext()); // Act List<WeightEntry> entries = repos.GetAllWeightEntries(); // Assert Assert.AreEqual(5, entries.Count); }
和我的MockWeightTrackerContext:
class MockWeightTrackerContext : IWeightTrackerContext { public MockWeightTrackerContext() { WeightEntries = new DbSet<WeightEntry>(); WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 }); WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 }); WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 }); WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 }); WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 }); } public DbSet<WeightEntry> WeightEntries { get;set; } public int SaveChanges() { throw new NotImplementedException(); } }
当我试图build立一些testing数据,因为我不能创build一个DbSet<>
因为它没有构造函数,所以我的问题发生。 我觉得我正在用我整个方法来嘲弄我的背景的错误的树。 任何build议将非常欢迎这个完整的unit testing新手。
您可以通过Context上的Factory方法Set()创build一个DbSet,但是您不希望在unit testing中对EF有依赖性。 因此,你需要看的是使用IDbSet接口或使用诸如Moq或RhinoMock之类的Mocking框架之一的Stub来实现DbSet的存根。 假设你写了自己的Stub,你只需要将WeightEntry对象添加到内部的哈希集合中。
如果您searchObjectSet和IObjectSet,您可能有更多的运气学习unit testingEF。 这些是代码第一个CTP版本之前的DbSet的对应,并且从unit testing的angular度来看,他们有更多的关于它们的文章。
这里有一篇关于MSDN的文章 ,讨论EF代码的可testing性。 它使用IObjectSet,但我认为它仍然是相关的。
作为对David评论的回应,我在下面添加这个附录,因为它不适合在-comments中。 不确定这是否是长时间评论回复的最佳做法?
您应该更改IWeightTrackerContext接口以从WeightEntries属性(而不是DbSet具体types)返回一个IDbSet。 然后,您可以使用模拟框架(推荐)或您自己的自定义存根创buildMockContext。 这将从WeightEntries属性返回一个StubDbSet。
现在你还将拥有代码(即自定义存储库),这些代码依赖于IweightTrackerContext,在你的entity framework中,WeightTrackerContext将实现IWeightTrackerContext。 这往往是通过使用IoC框架(如Unity)的构造函数注入来完成的。 为了testing依赖于EF的存储库代码,你需要传递你的MockContext实现,这样被testing的代码就会认为它正在和“真正的”EF和数据库进行交互,并且按照预期行事(希望)。 由于您已经删除了可变外部数据库系统和EF的依赖关系,因此您可以在unit testing中可靠地validation存储库调用。
模拟框架的很大一部分是提供validationMock对象的调用来testing行为的能力。 在上面的示例中,您的testing实际上只是testingDbSet添加function,这不应该成为您的担心,因为MS将为此进行unit testing。 你想知道的是,对DbSet的Add的调用是在你自己的仓库代码中进行的,如果合适的话,那就是Mock框架的来源。
对不起,我知道这是很多消化,但如果你有通读这篇文章,很多将变得更清晰,因为斯科特·艾伦是比我更好地解释这个东西:)
基于Daz所说的话(正如我在他的评论中提到的那样),你将需要对IDbSet进行“假”实现。 CTP 4的例子可以在这里find,但要得到它的工作,你将不得不定制你的查找方法,并添加一些先前void-ed方法的返回值,如添加。
以下是我自己制作的CTP 5示例:
public class InMemoryDbSet<T> : IDbSet<T> where T : class { readonly HashSet<T> _data; readonly IQueryable _query; public InMemoryDbSet() { _data = new HashSet<T>(); _query = _data.AsQueryable(); } public T Add(T entity) { _data.Add(entity); return entity; } public T Attach(T entity) { _data.Add(entity); return entity; } public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T { throw new NotImplementedException(); } public T Create() { return Activator.CreateInstance<T>(); } public virtual T Find(params object[] keyValues) { throw new NotImplementedException("Derive from FakeDbSet and override Find"); } public System.Collections.ObjectModel.ObservableCollection<T> Local { get { return new System.Collections.ObjectModel.ObservableCollection<T>(_data); } } public T Remove(T entity) { _data.Remove(entity); return entity; } public IEnumerator<T> GetEnumerator() { return _data.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _data.GetEnumerator(); } public Type ElementType { get { return _query.ElementType; } } public Expression Expression { get { return _query.Expression; } } public IQueryProvider Provider { get { return _query.Provider; } } }
你可能会得到一个错误
AutoFixture was unable to create an instance from System.Data.Entity.DbSet`1[...], most likely because it has no public constructor, is an abstract or non-public type.
DbSet有一个受保护的构造函数。
通常用于支持可testing性的方法创build您自己的Db集的实现
public class MyDbSet<T> : IDbSet<T> where T : class {
使用这个
public class MyContext : DbContext { public virtual MyDbSet<Price> Prices { get; set; } }
如果你看下面的SO问题,2ns的答案,你应该能够find一个自定义DbSet的完整实现。
使用EF4“Code First”和Repository进行unit testing
那么
var priceService = fixture.Create<PriceService>();
不应该抛出exception。