2016-07-26 63 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; 
}