EntityFramework核心测试内存数据库错误

时间:2016-07-26 17:33:23

标签: c# entity-framework unit-testing

我正在使用EntityFrameworkCore 1.0并试图设置一些简单的单元测试,但是不断收到错误,结果是“已经添加了一个具有相同键的项目。键:-10。我不明白如何防止这种情况发生。我明白问题是什么,但不知道为什么以及如何解决。

首先,我的测试看起来像是:

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
            });
        }
    }

所以你可以看到它只是在检查它还没有被添加之后向每个DbSet添加几个项目。现在,如果我运行此测试,它将全天工作,此时没有任何问题。

当我添加第二个测试时,问题就出现了,就像这样

    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类中的所有静态内容(类,方法等)并使用实例变量,但这没有任何区别。

我在这里缺少什么。我想为每个测试生成一个单独的内存数据库,以便它们之间没有依赖关系。

1 个答案:

答案 0 :(得分: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;
}