2015-11-26 42 views
7

我正在爲一個項目編寫自動化測試,我一直致力於更熟悉MVC,EntityFramework(代碼優先),單元測試和Moq。DbContext.ChangeTracker在自動化測試中拋出SQLException

我在Repository類部,其設定我的模型的LastModified場每當Repository.SaveChanges()由這樣的作品(MyModelBase是一個基類)控制器稱爲:

public void RepoSaveChanges() 
{ 
    foreach(var entry in _dbContext.ChangeTracker.Entities().Where(e => e.State == EntityState.Modified)) 
    { 
     MyModelBase model = entry.Entity as MyModelBase; 
     if (model != null) 
     { 
      model.LastModified = DateTime.Now; 
     } 
    } 
    _dbContext.SaveChanges(); 
} 

也能正常工作期間應用程序運行時間在正常的在線環境中,但在運行測試方法時會中斷。我使用Moq來模擬DbContext中的DbSets並設置我的測試數據。

下面是我怪異的一部分: 我的單元測試運行正常(合格),但他們永遠無法真正進入foreach循環 - 它掛在ChangeTracker.Entities()訪問和退出循環,跳下來_dbContext.SaveChanges()。沒有錯誤。

但是,在與我共享項目的朋友的機器上,當訪問ChangeTracker.Entities()時,他得到SQLException。我確實在VS2015中檢查了SQLExceptions,並且沒有輸出或其他異常。

Result StackTrace:
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection) ....

Result Message:
Test method MyProject.Tests.TestClasses.MyControllerTests.TestCreate threw exception: System.Data.SqlClient.SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

最後,我的問題是:是否有使用起訂量嘲笑ChangeTracker(我懷疑不是從先前的調查)的方式,或者是有另一種方法我可以把我的RepoSaveChanges()自動設置的屬性?沒有訪問ChangeTracker.Entities()我需要有更新邏輯設置LastModified字段爲每個模型類型。同樣,我覺得要避免使用該API /框架的一部分,因爲測試是固執的並不理想。

有沒有人有任何洞察力,爲什麼不拋出SQLException /不能在我的機器上捕獲?或者有關如何在單元測試中使用ChangeTracker.Entities()的建議?作爲最後手段,我只會在所有模型和控制器中單獨設置LastModified屬性。

更新: 更多示例代碼已被要求,所以讓我再詳細。我嘲笑一個的DbContext使用最小起訂量,然後嘲笑包含在的DbContext的DbSet對象:

var mockContext = new Mock<MyApplicationDbContext>(); //MyApplicationDbContext extends DbContext 

Person p = new Person(); 
p.Name = "Bob"; 
p.Employer = "Superstore"; 

List<Person> list = new List<Person>(); 
list.Add(p); 

var queryable = list.AsQueryable(); 

Mock<DbSet<Person>> mockPersonSet = new Mock<DbSet<Person>>(); 
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Provider).Returns(queryable.Provider); 
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Expression).Returns(queryable.Expression); 
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.ElementType).Returns(queryable.ElementType); 
mockPersonSet.As<IQueryable<Person>>().Setup(set => set.GetEnumerator()).Returns(() => queryable.GetEnumerator()); 

DbSet<Person> dbSet = mockPersonSet.Object as DbSet<Person>; 
mockPersonSet.Setup(set => set.Local).Returns(dbSet.Local); 

mockContext.Setup(context => context.Set<Person>()).Returns(mockPersonSet.Object); 
mockContext.Setup(context => context.Persons).Returns(mockPersonSet.Object)); 

//Create the repo using the mock data context I created 
PersonRepository repo = new PersonRepository(mockContext.Object); 

//Then finally create the controller and perform the test 
PersonController controller = new PersonController(repo); 
var result = controller.Create(someEmployerID); //Sometimes throws an SQLException when DbContext.SaveChanges() is called 
+0

這是什麼版本的EF? –

+0

@ E-BAT EF版本6.1.3 – Softerware

回答

2

我對自己提出了一個不太理想的解決方案,但足以讓我繼續前進。

public class MyApplicationDbContext : IdentityDbContext<MyApplicationUser> 
{ 
    //DbSets etc 

    public virtual IEnumerable<MyModelBase> AddedEntries 
    { 
     get 
     {    
      foreach (var entry in ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added)) 
      { 
       MyModelBase model = entry.Entity as MyModelBase; 
       if (model != null) 
       { 
        yield return model; 
       } 
      } 
     } 
    } 
} 

這樣我仍然可以遍歷項()用於業務邏輯通過調用問題說明中描述:我通過簡單地增加一個抽象層,以我的類,它擴展DbContext稱爲MyApplicationDbContext規避了DbContext.ChangeTracker.Entries() API MyApplicationDbContext.AddedEntries而不是MyApplicationDbContext.ChangeTracker.Entries()。然而,因爲我做了財產virtual,我可以設置使用起訂量返回:

List<SomeModel> addedEntries = new List<SomeModel>(); 
addedEntries.add(someModelWhichWillBeAddedByTheController); 
mockContext.Setup(context => context.AddedEntries).Returns(addedEntries); 

這種方式使用AddedEntries屬性時,控制器將觀察someModelWhichWillBeAddedByTheController。不利的一面是我無法測試真實業務邏輯中使用的代碼,但通過使用測試數據庫實現集成測試,我將能夠實現這一點。

我從來沒有找到SQLException被扔在一臺機器上而不是其他的原因。

0

什麼你的錯誤的意思是,它不能創建到數據庫的連接。這發生在創建DbContext時,EF將開始執行諸如檢查數據庫是否需要遷移等操作。

在我看來,你需要模擬你的dbcontext的構造函數。我不太熟悉moq本身,但是如果我正確地讀你的代碼,我沒有看到你嘲笑構造函數。

+0

我沒有看到模仿構造函數的任何價值,因爲我的測試不會連接到任何數據庫,我需要能夠離線使用DbContext.ChangeTracker.Entries()API 。我能做的最好的是提供一個替代連接字符串,以便它無法連接到。 – Softerware

+0

我的觀點是DbContext需要數據庫連接。如果你想要一個模擬DbContext,那麼你應該嘲笑構造函數,所以當你的DbContext創建時,你會得到一個假版本 - 一個沒有數據庫連接 – Batavia

0

異常說它不能創建到數據庫的連接,我懷疑你和你的朋友在app.config文件中有不同的連接字符串,或者你的朋友沒有訪問數據庫的權限。

其實你正在編寫一個集成測試,而不是單元測試。單元測試是專門爲被測試的對象編寫的。在你的情況代碼:

PersonController controller = new PersonController(); 
var result = controller.Create(someEmployerID); 

不應該使用Repository的實際執行。您需要將存儲庫的模擬實例注入PersonController。我假設你沒有使用任何IoC containtainers所以能夠把它注入到你的控制器,你可以添加其他的構造函數:

private IRepository _repository;  
public PersonController() : this(new Repository()) //real implementation 
{} 

public PersonController(IRepository repository) 
{ 
    _repository = repository; 
} 

// your test 
var reporitory = new Mock<IRepository>(); 
var controller = new PersonController(repository.Object); 
controller.CreateEmployee(someId); 
// assert that your repository was called 
repository.Verify(...); 

這種技術被稱爲Poor Man's injection,它不是注射的推薦方式,但它是比僅具體實例更好。

接下來,如果你想寫單元測試(不整合)爲您Repository,那麼你需要有一個像IDbContext的接口,這將是你的DbContext周圍的包裝。併爲您的Repository創建2個構造函數,如PersonController - 無參數和IDbContext

更新:無視我關於RepositoryDbContext的最後陳述。我檢查了文檔DbChangeTracker.Entries()方法不是虛擬的,這意味着您將無法使用Moq庫來嘲笑它。您需要使用another mocking framework或使用集成測試(無模擬實例)進行測試。

+0

嗨,謝謝你的輸入。實際上,我錯誤地將該部分從示例代碼中刪除了。我使用一個存儲庫,允許我將我的模擬DbContext傳遞給它。這不像你提到的那樣是一個「真正的實現」。我控制器上的所有HTTP GET方法都可以正常工作,當回購中的某些邏輯試圖利用DbContext時,問題就出現了。ChangeTracker.Entries()API。 – Softerware