2015-09-13 77 views
7

我想弄明白单元测试中的嘲弄,并将单元测试过程集成到我的项目中。所以我一直在通过几个教程,并重构我的代码来支持嘲笑,无论如何,我无法通过测试,因为我试图测试的数据库方法是使用事务,但创建事务时,我得到获取过去的实体框架BeginTransaction

底层提供程序在打开时失败。

没有交易一切正常。

我目前拥有的代码是:被测

[TestMethod] 
public void Test1() 
{ 
    var mockSet = GetDbMock(); 
    var mockContext = new Mock<DataContext>(); 
    mockContext.Setup(m => m.Repository).Returns(mockSet.Object); 

    var service = new MyService(mockContext.Object); 
    service.SaveRepository(GetRepositoryData().First()); 
    mockSet.Verify(m => m.Remove(It.IsAny<Repository>()), Times.Once()); 
    mockSet.Verify(m => m.Add(It.IsAny<Repository>()), Times.Once()); 
    mockContext.Verify(m => m.SaveChanges(), Times.Once()); 
} 

// gets the DbSet mock with one existing item 
private Mock<DbSet<Repository>> GetDbMock() 
{ 
    var data = GetRepositoryData(); 
    var mockSet = new Mock<DbSet<Repository>>(); 

    mockSet.As<IQueryable<Repository>>().Setup(m => m.Provider).Returns(data.Provider); 
    // skipped for brevity 
    return mockSet; 
} 

代码:

private readonly DataContext _context; 
public MyService(DataContext ctx) 
{ 
    _context = ctx; 
} 

public void SaveRepositories(Repository repo) 
{ 
    using (_context) 
    { 
     // Here the transaction creation fails 
     using (var transaction = _context.Database.BeginTransaction()) 
     { 
      DeleteExistingEntries(repo.Id); 
      AddRepositories(repo); 
      _context.SaveChanges(); 
      transaction.Commit(); 
     } 
    } 
} 

我试图嘲弄交易部分,以及:

var mockTransaction = new Mock<DbContextTransaction>(); 
mockContext.Setup(x => x.Database.BeginTransaction()).Returns(mockTransaction.Object); 

但这不起作用,失败:

无效设置在非虚拟(在VB重写)成员:CONN => conn.Database.BeginTransaction()

任何想法如何解决此问题?

回答

5

由于第二个错误消息说,Moq不能模拟非虚方法或属性,所以这种方法将无法工作。我建议使用Adapter pattern来解决这个问题。这个想法是创建一个适配器(一个实现某个接口的包装类),与DataContext交互,并通过该接口执行所有数据库活动。然后,您可以改为使用界面。

public interface IDataContext { 
    DbSet<Repository> Repository { get; } 
    DbContextTransaction BeginTransaction(); 
} 

public class DataContextAdapter { 
    private readonly DataContext _dataContext; 

    public DataContextAdapter(DataContext dataContext) { 
     _dataContext = dataContext; 
    } 

    public DbSet<Repository> Repository { get { return _dataContext.Repository; } } 

    public DbContextTransaction BeginTransaction() { 
     return _dataContext.Database.BeginTransaction(); 
    } 
} 

你的所有代码,以前使用的DataContext直接现在应该使用IDataContext,这应该是一个DataContextAdapter程序运行时,但在测试中,你可以很容易地嘲笑IDataContext的。这应该使嘲笑方式更简单,因为您可以设计IDataContextDataContextAdapter以隐藏实际DataContext的一些复杂性。

+0

这种设计模式很有意义,我会努力去理解它。尽管我仍然无法实现它,因为我不能嘲笑'DbContextTransaction',因为它只有内部构造函数,这似乎是'Moq'的一个问题。无论如何,它认为测试执行并不依赖于它的工作,如果我不经过调试就运行测试,它们会通过,除非我明确检查是否有任何异常被抛出。这很好。感谢洞察力,除非有更好的发现,否则我会接受。 (y) –

+0

你可以在那里得到同样的技巧 - 创建一个由'DbContextTransactionAdapter'实现的'IDbContextTransaction'接口,它包装一个实际的'DbContextTransaction' - 并且在你的测试中模拟'IDbContextTransaction'。这有点麻烦,但这个技巧通常会使使用库代码的代码更容易测试,并且与库更紧密地耦合。 –

+0

最近有一件事让我惊讶,当我做了Mock.Of 是模拟的数据库属性创建一个具体的数据库对象使用app.config中的连接信息。因此,任何Database.ExecuteSql都可能会针对该数据库运行。还从BeginTransaction返回了具体的事务。这是怎么回事,一个模拟回报真实的东西? –