2011-09-02 72 views
6

我决定开始在我们的应用程序中编写单元测试。它使用实体框架和存储库模式。单元测试EF存储库模式与Moq

现在我想开始测试使用存储库的逻辑类。我在这里提供一个简单的例子。

public class GenericRepository : IRepository 
{ 
    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

一个简单的逻辑类从日历表中降序排列返回不同年(是的,我知道这个词的日历在我们的代码拼写错误):

我在班上GenericRepository三种方法

public class GetDistinctYearsFromCalendar 
{ 
    private readonly IRepository _repository; 

    public GetDistinctYearsFromCalendar() 
    { 
     _repository = new GenericRepository(); 
    } 

    internal GetDistinctYearsFromCalendar(IRepository repository) 
    { 
     _repository = repository; 
    } 

    public int[] Get() 
    { 
     return _repository.Find<Calender_Tbl>(c => c.Year.HasValue).Select(c => c.Year.Value).Distinct().OrderBy(c => c).Reverse().ToArray(); 
    } 
} 

这是我的第一个测试:

[TestFixture] 
public class GetDistinctYearsFromCalendarTest 
{ 
    [Test] 
    public void ReturnsDistinctDatesInCorrectOrder() 
    { 
     var repositoryMock = new Mock<IRepository>(); 

     repositoryMock.Setup(r => r.Find<Calender_Tbl>(c => c.Year.HasValue)).Returns(new List<Calender_Tbl> 
     { 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 1, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2010, 2, 1), 
        Year = 2010 
       }, 
      new Calender_Tbl 
       { 
        Date = 
         new DateTime(2011, 1, 1), 
        Year = 2011 
       } 
     }.AsQueryable()); 

     var getDistinct = new GetDistinctYearsFromCalendar(repositoryMock.Object).Get(); 

     Assert.AreEqual(2, getDistinct.Count(), "Returns more years than distinct."); 
     Assert.AreEqual(2011, getDistinct[0], "Incorrect order, latest years not first."); 
     Assert.AreEqual(2010, getDistinct[1], "Wrong year."); 


    } 
} 

这工作正常。但这实际上并不是我想要做的。由于我必须在模拟对象上设置方法Find,所以我还需要知道它是如何在我的逻辑类中调用的。如果我想做TDD,我不想介意这个。我想知道的是我的存储库应该提供哪些日历实体。我想设置GetQuery方法。 像这样:

repositoryMock.Setup(r => r.GetQuery<Calender_Tbl>()).Returns(new List<Calender_Tbl> 
{ 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 1, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2010, 2, 1), 
      Year = 2010 
     }, 
    new Calender_Tbl 
     { 
      Date = 
       new DateTime(2011, 1, 1), 
      Year = 2011 
     } 
}.AsQueryable()); 

所以,当发现是在GenericRepository类内部调用GetQuery应该得到正确的日历实体,我在安装GetQuery。但这当然不起作用。由于我没有设置我的模拟对象的查找方法,我没有得到任何实体。

那该怎么办?当然,我可以使用鼹鼠或其他一些模拟一切的框架,但我不想那样做。在课堂设计或测试中解决问题有什么我可以做的吗?

如果我不得不使用当前的解决方案,那么世界并不是世界末日,但如果财产年度变成不可空的整数?那么当然,我将不得不在逻辑类中更改我的实现,但我也必须更改测试。我想尽量避免这种情况。

+1

我认为这样做是有“GetDistinctYearsFromCalendar”依赖于“IRepository”的一种方式。你可以模拟出“GenericRepository”的测试这样“GetDistinctYearsFromCalendar”隔离。有关更多详细信息,请参阅http://en.wikipedia.org/wiki/Dependency_injection。 – Michael

+0

好吧,我没有看到内部ctor。你在同一个项目中有测试和生产代码吗?我认为正确的做法是完成依赖注入并跳过默认的ctor。 – Michael

+0

我知道我可以只写我的逻辑为: 返回_repository.GetQuery (),其中(C => c.Year.HasValue)。选择(C => c.Year.Value).Distinct()。 OrderBy(c => c).Reverse()。ToArray(); 没有我一直测试到另一个项目,但暴露的内部用汇编指令的测试项目。 但是,如果我从来不使用另一种类型比GenericRepository呢?那么是否需要一直进行依赖注入? 这似乎是最好的解决方案吗?我是盲目的,想要使用存储库的所有功能。 – John

回答

12

我可以看到两种方式:

public class MockRepository : IRepository 
{ 
    private List<object> entities; 
    public MockRepository(params object[] entitites) 
    { 
     this.entities = entities.ToList(); 
    } 

    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     return this.entities.OfType<TEntity>().AsQueryable(); 
    } 

    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

这是最简单的,我的首选方式。 Moq不是一切的锤子;)

或者,如果你真的坚持使用Moq(我很受宠,但在这种情况下非常没必要,因为你可以对返回的实体进行基于状态的测试) ,你可以这样做:

public class GenericRepository : IRepository 
{ 
    public virtual IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class 
    { 
     var entityName = GetEntityName<TEntity>(); 
     return Context.CreateQuery<TEntity>(entityName); 
    } 
    private string GetEntityName<TEntity>() where TEntity : class 
    { 
     return typeof(TEntity).Name; 
    } 
    public IEnumerable<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class 
    { 
     return GetQuery<TEntity>().Where(predicate).AsEnumerable(); 
    } 
} 

然后用最小起订量来覆盖GetQuery的行为:

var repository = new Mock<GenericRepository> { CallBase = true }; 

repository.Setup(x => x.GetQuery<Foo>()).Returns(theFoos.AsQueryable()); 

会发生什么事是查找方法会在GenericRepository类执行,这将反过来GetQuery已被Moq覆盖提供de固定的一组实体。

我明确设置了CallBase = true,以防万一您偶然也会发现Find virtual,以便确保始终调用它。如果Find不是虚拟的,那么在技术上不需要,因为它总是会在模拟从继承/嘲讽的实际类中调用。

我会选择第一个选项,更容易理解发生了什么,它可以在单个特定测试的上下文之外重复使用(只需传递任何需要的实体,它将适用于所有内容)。

+0

因为我知道嘲笑返回IQueryable的方法是不可测试的,因为你必须运行数据库的集成测试我不知道是否有可能嘲笑方法查找并让它返回我的实体,无论我传递什么参数?我尝试过It.IsAny <>,但它没有奏效。 – John

0

最近,一个名为Effort的新工具出现在EF 6+上,我发现它对于针对假DB的单元测试非常有帮助。请参阅http://effort.codeplex.com/wikipage?title=Tutorials&referringTitle=Home

通过使用这个包管理器控制台命令添加:

PM> Install-Package Effort.EF6 

然后添加一个界面为你的DbContext,比方说,如果你使用的是AdventureWorks数据库(见https://sql2012kitdb.codeplex.com/):

然后更新您的的DbContext添加两个新参数的构造函数:

/// 
    /// Create a new context based on database name or connection string. 
    /// 
    /// Database name or connection string 
    public AdventureWorksEntities(string nameOrConnectionString) 
     : base(nameOrConnectionString) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

    public AdventureWorksEntities(DbConnection connection) 
     : base(connection, true) 
    { 
     this.Configuration.LazyLoadingEnabled = false; 
    } 

添加一个构造函数的接口,你的资料库:

private IAdventureWorksDbContext _dbContext; 

    public ProductRepository(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Configuration.AutoDetectChangesEnabled = false; 
     this._dbContext = dbContext; 
    } 

然后将接口添加到您的单元测试的项目和关联类:

public interface ITestDatabase : IDisposable 
{ 
    IAdventureWorksDbContext CreateContext(); 

    void Dispose(IAdventureWorksDbContext context); 
} 

一些假的数据添加到您的单元测试项目:

public class ProductsTestData 
{ 
    public static void AddTestData(IAdventureWorksDbContext dbContext) 
    { 
     dbContext.Products.Add(new Product() { Id = new Guid("23ab9e4e-138a-4223-bb42-1dd176d8583cB"), Name = "Product A", CreatedDate = DateTime.Now, Description = "Product description..." }); 
     dbContext.Products.Add(new Product() { Id = new Guid("97e1835f-4c1b-4b87-a514-4a17c019df00"), Name = "Product B", CreatedDate = DateTime.Now }); 
     dbContext.SaveChanges(); 
    } 
} 

现在设置你的单元测试类:

[TestClass] 
public class ProductsTest 
{ 
    private ITestDatabase _testDatabaseStrategy; 
    private ProductRepository _productRepository; 
    private IAdventureWorksDbContext _context; 

    [TestInitialize] 
    public void SetupTest() 
    { 
     // create the test strategy. This will initialise a new database 
     _testDatabaseStrategy = CreateTestStrategy(); 

     // add test data to the database instance 
     using (_context = _testDatabaseStrategy.CreateContext()) 
     { 
      ProductsTestData.AddTestData(_context); 
      _context.SaveChanges(); 
     } 

     // initialise the repository we are testing 
     _context = _testDatabaseStrategy.CreateContext(); 
     _productRepository = new ProductRepository(_context); 
    } 

    protected ITestDatabase CreateTestStrategy() 
    { 
     return new EffortDatabaseStrategy(); 
    } 

    [TestCleanup] 
    public void CleanupTest() 
    { 
     // dispose of the database and connection 
     _testDatabaseStrategy.Dispose(_context); 
     _context = null; 
    } 

    [TestMethod] 
    public void GetProductsByTagName() 
    { 
     IEnumerable<Product> products = _productRepository.GetProductsByTagName("Tag 1", false); 
     Assert.AreEqual(1, products.Count()); 
    } 

Wh ERE EffortDatabaseStrategy是:

public class EffortDatabaseStrategy : ITestDatabase 
{ 
    public EffortDatabaseStrategy() 
    { 
    } 

    private DbConnection _connection; 

    public IAdventureWorksDbContext CreateContext() 
    { 
     if (_connection == null) 
     { 
      _connection = Effort.DbConnectionFactory.CreateTransient(); 
     } 
     var context = new AdventureWorksDbContext(_connection); 

     return context; 
    } 

    public void Dispose(IAdventureWorksDbContext context) 
    { 
     if (context != null) 
     { 
      context.Dispose(); 
     } 
    } 

    public void Dispose() 
    { 
    } 
} 

有关详情,请参阅http://www.codeproject.com/Articles/460175/Two-strategies-for-testing-Entity-Framework-Effort?msg=5122027#xx5122027xx