2015-09-13 103 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返回了具體的事務。這是怎麼回事,一個模擬回報真實的東西? –