使用EF4"Code First"和存储库进行单元测试

Dav*_*uge 49 c# asp.net-mvc unit-testing mocking entity-framework-4

我试图处理单元测试一个非常简单的ASP.NET MVC测试应用程序,我使用最新的EF4 CTP中的Code First方法构建.我对单元测试/模拟等不太熟悉.

这是我的Repository类:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是IWeightTrackerContext

public interface IWeightTrackerContext
{
    DbSet<WeightEntry> WeightEntries { get; set; }
    int SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)

......及其实现,WeightTrackerContext

public class WeightTrackerContext : DbContext, IWeightTrackerContext
{
    public DbSet<WeightEntry> WeightEntries { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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

[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);
}
Run Code Online (Sandbox Code Playgroud)

还有我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

当我试图建立一些测试数据时,我的问题就出现了,因为我无法创建,DbSet<>因为它没有构造函数.我感觉自己正在用我的整个方法咆哮错误的树,试图模仿我的背景.任何建议都非常欢迎这个完整的单元测试新手.

Dar*_*wis 48

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

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

这是一篇关于MSDN 的优秀文章,讨论了EF代码的可测试性.它使用IObjectSet但我认为它仍然相关.

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

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

现在,您还将拥有依赖于IWeightTrackerContext的代码(即自定义存储库),您将在生成中传递实现IWeightTrackerContext的Entity Framework WeightTrackerContext.这往往是通过使用Unity等IoC框架进行构造函数注入来完成的.为了测试依赖于EF的存储库代码,您将传入MockContext实现,以便测试中的代码认为它正在与"真正的"EF和数据库进行通信,并按预期行事(希望如此).由于已删除了对可更改的外部数据库系统和EF的依赖性,因此可以在单元测试中可靠地验证存储库调用.

模拟框架的很大一部分是提供验证Mock对象上的调用以测试行为的能力.在上面的示例中,您的测试实际上只测试DbSet添加功能,这不应该是您关注的问题,因为MS将对其进行单元测试.你想知道的是,对DbSet上的Add的调用是在你自己的存储库代码中进行的,如果合适的话,这就是Mock框架的用武之地.

对不起,我知道这有很多东西要消化,但是如果你读完那篇文章,那么很多东西会变得更加清晰,因为斯科特艾伦在解释这些东西方面比我好多了:)


Mer*_*itt 41

在Daz所说的基础上(正如我在他的评论中提到的那样),你将需要制作一个IDbSet的"假"实现.对于CTP 4的例子可以找到这里,但得到它的工作,你就必须自定义您的查找方法,并添加返回值的一对夫妇先前空隙编辑方法,如添加.

以下是我自己为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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 有用的课程.感谢分享! (3认同)