2016-07-26 60 views
1

我正在使用EntityFrameworkCore 1.0,並試圖設置一些簡單的單元測試,但不斷收到錯誤,大意是「添加了同一個鍵的項目。 。10.我不明白如何防止這種情況的發生我不明白的問題是什麼,但不是爲什麼,以及如何解決EntityFramework核心測試內存數據庫錯誤

首先我有一個測試,看起來像:

public void CanLoadAllAnnouncements() 
{ 
    var service = new AnnouncementControllerService(GenerateTestData.Generate()); 

    var results = service.Get(); 
    Assert.Equal(3, results.Count); 
    Assert.Equal("Test Announcement 1", results[0].Message); 
    Assert.Equal("Test Announcement 2", results[1].Message); 
    Assert.Equal("Test Announcement 3", results[2].Message); 
} 

你可以看到這是調用一個控制器服務類傳遞正在生成的內存數據庫,這個類的代碼看起來像這樣。

public const string CurrentUserName = "testUserName"; 
    public const string AlternateUserName = "anotherUserName"; 

    public static DataContext Generate() 
    { 
     var context = new DataContext(); 

     CreateRecentActivity(context); 
     CreateAnnouncement(context); 

     context.SaveChanges(); 

     return context; 
    } 

    private static void CreateAnnouncement(DataContext context) 
    { 
     AddAnnouncement(context, -20, "Test Announcement 1", 1); 
     AddAnnouncement(context, -21, "Test Announcement 2", 2); 
     AddAnnouncement(context, -22, "Test Announcement 3", 3); 
    } 

    private static void CreateRecentActivity(DataContext context) 
    { 
     AddRecentActivity(context, -10, "Test Result 1", "#/TestResult1", CurrentUserName); 
     AddRecentActivity(context, -11, "Test Result 2", "#/TestResult2", CurrentUserName); 
     AddRecentActivity(context, -12, "Test Result 3", "#/TestResult3", CurrentUserName); 
     AddRecentActivity(context, -13, "Another Test Result 1", "#/AnotherTestResult1", AlternateUserName); 
     AddRecentActivity(context, -14, "Another Test Result 2", "#/AnotherTestResult2", AlternateUserName); 
     AddRecentActivity(context, -15, "Another Test Result 3", "#/AnotherTestResult3", AlternateUserName); 
    } 

    private static void AddAnnouncement(DataContext context, int id, string message, int ordering) 
    { 
     if (context.Announcements.All(ra => ra.Id != id)) 
     { 
      context.Announcements.Add(new Announcement 
      { 
       Id = id, 
       Message = message, 
       Ordering = ordering 
      }); 
     } 
    } 

    private static void AddRecentActivity(DataContext context, int id, string name, string url, string userName) 
    { 
     if (context.RecentActivities.All(ra => ra.Id != id)) 
     { 
      context.RecentActivities.Add(new RecentActivity 
      { 
       Id = id, 
       Name = name, 
       Url = url, 
       UserName = userName 
      }); 
     } 
    } 

所以你可以看到它只是在檢查到它還沒有被添加之後,向每個DbSets添加一些項目。現在,如果我運行這個測試,它會整天工作,沒有任何問題。

問題進場時,我添加了第二次測試,這樣的事情

public void CanLoadAllRecentItemsForCurrentUser() 
    { 
     var service = new RecentActivityControllerService(GenerateTestData.Generate()); 

     var results = service.Get(Testing.GenerateTestData.CurrentUserName); 
     Assert.Equal(3, results.Count); 
     Assert.Equal("Test Result 1", results[0].Name); 
     Assert.Equal("Test Result 2", results[1].Name); 
     Assert.Equal("Test Result 3", results[2].Name); 
    } 

你可以看到,這個測試是非常相似的前一個。完成同樣的事情,創建控制器服務,傳入在generate方法中構建的db上下文的新實例。

這裏是出現錯誤的地方。我假設問題是,上下文被生成爲一個靜態(即使我每次生成一個單獨的實例)。並且由於測試正在並行運行,因此使用導致錯誤的相同密鑰添加相同的項目。

我已經嘗試刪除GenerateTestData類(類,方法等)中靜態的一切,並使用實例變量,但這沒有什麼區別。

我在這裏錯過了什麼。我想爲每個測試生成一個單獨的內存數據庫,以便它們之間不存在依賴關係。

回答

4

我找到了答案,這在鏈接https://docs.efproject.net/en/latest/miscellaneous/testing.html

的基本想法是,你需要指定一個DbContextOptions到您的數據上下文的構造,以確保它創建一個單獨的乾淨的情況下爲每個測試。

public static DataContext Generate() 
{ 
    var options = CreateNewContextOptions(); 
    var context = new DataContext(options); 

    CreateRecentActivity(context); 
    CreateAnnouncement(context); 

    context.SaveChanges(); 

    return context; 
} 

private static DbContextOptions<DataContext> CreateNewContextOptions() 
{ 
    // Create a fresh service provider, and therefore a fresh 
    // InMemory database instance. 
    var serviceProvider = new ServiceCollection() 
     .AddEntityFrameworkInMemoryDatabase() 
     .BuildServiceProvider(); 

    // Create a new options instance telling the context to use an 
    // InMemory database and the new service provider. 
    var builder = new DbContextOptionsBuilder<DataContext>(); 
    builder.UseInMemoryDatabase() 
      .UseInternalServiceProvider(serviceProvider); 

    return builder.Options; 
}