2010-08-11 29 views
49

我试图获得单元测试一个非常简单的ASP.NET MVC测试应用程序,我已经使用最新的EF4 CTP中的代码优先方法构建的句柄。我不是很经验与单元测试/嘲讽等单元测试EF4“代码优先”和存储库

这是我的仓库类:

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; } 
} 

在我的测试中,我有以下内容:

[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:当我试图建立一些测试数据,我不能创建一个DbSet<>,因为它没有构造发生

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(); 
    } 
} 

我的问题。我用我的整个方法试图嘲弄我的背景,我感到我在吠叫错误的树。任何建议将非常欢迎这个完整的单元测试新手。

回答

48

您可以通过上下文的Factory方法Set()创建DbSet,但您不希望在单元测试中对EF有任何依赖性。因此,您需要考虑的是使用IDbSet接口或使用Moq或RhinoMock之类的Mocking框架之一的Stub实现DbSet的存根。假设你编写了自己的Stub,你只需要将WeightEntry对象添加到内部哈希集。

如果您搜索ObjectSet和IObjectSet,您可能会有更多的运气学习单元测试EF。这些是代码第一个CTP版本之前的DbSet的对应物,并且从单元测试的角度来看,它们有更多的关于它们的文章。

这是MSDN上的excellent article,它讨论了EF代码的可测试性。它使用IObjectSet,但我认为它仍然相关。

作为对David评论的回应,我在下面添加了这个附录,因为它不适合-comments。不确定这是否是长评论回复的最佳做法?

您应该更改IWeightTrackerContext接口以从WeightEntries属性而不是DbSet具体类型返回IDbSet。然后,您可以使用模拟框架(推荐)或您自己的自定义存根创建MockContext。这将从WeightEntries属性返回一个StubDbSet。

现在您还将拥有取决于IweightTrackerContext的代码(即Custom Repositories),您将在实体框架WeightTrackerContext中传递实现IWeightTrackerContext的生产。这往往是通过使用IoC框架(如Unity)的构造函数注入完成的。为了测试依赖于EF的存储库代码,您需要传递MockContext实现,以便测试中的代码认为它正在与“真实”EF和数据库进行交谈,并按照预期行为(希望)。由于您已经删除了可变外部数据库系统和EF的依赖关系,因此您可以在单元测试中可靠地验证存储库调用。

模拟框架的很大一部分是提供验证模拟对象上的调用来测试行为的能力。在上面的示例中,您的测试实际上只是测试DbSet添加功能,这不应该成为您的担忧,因为MS将为此进行单元测试。你想知道的是,调用Add DbSet是在你自己的存储库代码中进行的,如果合适的话,那就是Mock框架的来源。

对不起,我知道这是很多消化,但如果你有一个通过这篇文章读了很多将变得更加清晰斯科特·艾伦在解释这个东西比我:)

+0

感谢您的链接,看起来一个伟大的文章。关于实现一个DbSet的存根,你是说我的MockWeightTrackerContext然后会返回,比如MyDbSetStub而不是DbSet?但是那个类不会实现这个接口。对不起,可能是愚蠢的问题。 ;) – DavidGouge 2010-08-12 09:42:16

+0

非常感谢您花时间解释这一点。这和你提供的链接有很大的帮助。如果我可以再次投票答复,我会。 :D – DavidGouge 2010-08-12 14:16:45

+0

谢谢,MSDN文章实现InMemoryObjectSet正是我需要让我的IContext正常工作。 – kamranicus 2011-02-10 07:07:45

41

建立在什么迪亚斯说,好了很多(和我在他的评论中提到),你将需要制作IDbSet的'假'实施。对于CTP 4的例子可以发现here但要得到它的工作,你必须自定义您的查找方法,并添加返回值的一对夫妇先前空隙编辑方法,如添加。

以下是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; } 
    } 
} 
+3

有用的类。感谢分享! – 2012-01-17 18:21:58

+0

这门课非常有帮助,谢谢! – JMGH 2012-11-21 19:49:11

+1

它可以更加有用做,如果你在构造函数中查找提供委托: 私人只读Func键<对象[],ISet的,T> _finder; 公共InMemoryDbSet(Func键<对象[],的ISet ,T>取景器) { _finder =取景器; _data = new HashSet (); _query = _data。AsQueryable已(); } 和 公共虚拟找不到(params对象[]键值) { 返回_finder(键值,_data); } – 2013-09-21 23:27:23

0

你可能得到一个错误

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有一个受保护的构造。

这种方法通常采取的支持可测试性创建自己的实现数据库设置

public class MyDbSet<T> : IDbSet<T> where T : class 
{ 

使用此作为

public class MyContext : DbContext 
{ 
    public virtual MyDbSet<Price> Prices { get; set; } 
} 

如果你看一下下面的SO问题,2ns的答案,你应该能够找到自定义DbSet的完整实现。

Unit testing with EF4 "Code First" and Repository

然后

var priceService = fixture.Create<PriceService>(); 

不应该抛出异常。

相关问题