如何对没有键的EF Core 3视图进行内存单元测试?

12

我正在尝试使用EF Core 3进行一些单元测试,并且似乎无法为视图设置测试数据。

当我尝试保存时,会出现以下错误:

无法跟踪此类型的实例,因为它没有主键。只能跟踪具有主键的实体类型

public class EFContext : DbContext
{
    public DbSet<ViewItem> ViewItems { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ViewItem>().HasNoKey().ToView("vTestView");
    }
}

using (EFContext efContext = new EFContext())
{
    efContext.ViewItems.Add(new ViewItem
    {
        Name = "This is test item #1"
    });

    efContext.SaveChanges();
}

1
我也想知道这个问题的解决方案。我迁移到了EF Core 3.0,现在看到了这个问题。我知道以前当它从数据库脚手架操作创建数据上下文时,会跳过没有主键的表,现在它会生成它,但是你只会得到这个错误。 - Darth Scitus
2
@CameronBelt 看起来这是一个开放问题,并且在微软的待办事项列表中 https://github.com/aspnet/EntityFramework.Docs/issues/898 - user3953989
2个回答

7

解决方法:IsInMemory()

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ViewItem>(entity =>
    {
        if (!Database.IsInMemory())
        {
            entity.HasNoKey();
            entity.ToView("vTestView");
        }
        else
        {
            entity.HasKey(e => e.Name);
        }
    });
}

更新:为了在测试时间和运行时保留受测属性的行为,您可以添加一个关键属性(按照约定)只在测试期间使用,并且在不进行测试时配置模型忽略该属性:

public class ViewItem 
{
    public int TestOnlyKey { get; set; }
    public string Name { get; set; }
}
public class EFContext : DbContext
{
    public DbSet<ViewItem> ViewItems { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ViewItem>(entity =>
        {
            if (!Database.IsInMemory())
            {
                entity.HasNoKey();
                entity.Ignore(e => e.TestOnlyKey);
                entity.ToView("vTestView");
            }
            else
            {
                entity.HasKey(e => e.TestOnlyKey);
            }
        });
    }
}

你显然没有测试过这个。在OnModelCreating中使用Database是不可能的,因为甚至还没有模型,更别说数据库对象了。而且,如果这是可能的,你在测试环境中将有一个完全不同的实体,使得测试几乎没有用处。 - Gert Arnold
微软文档暗示这种解决方法是一条推荐的路径:“提供程序还可以启用特定于特定数据存储的配置。” https://learn.microsoft.com/en-us/ef/core/modeling/. 当我自己测试时,在 OnModelCreating 中,我的 Database 属性被填充了“Microsoft.EntityFrameworkCore.InMemory”的 ProviderName。我通过传递由 DbContextOptionsBuilder<>().UseInMemoryDatabase() 创建的 DbContextOptions 来创建我的上下文。 - Sam
虽然这种解决方法确实会改变单元测试时的实体行为,但它仍然允许您对诸如Linq查询和对象转换之类的测试进行测试,这些测试是由您的查询产生的。 - Sam
1
好的,我有点羞愧地承认,在 OnModelCreating 中你可以做的事情很少,其中之一就是这个。但这仍然留下了第二个反对意见(这也是我投反对票的主要原因)。当数据库数据类型名称不同时,Db供应商特定的配置可能会有所帮助,而不是在根本上改变实体行为时。 - Gert Arnold
确实,它会修改行为,如果您不小心并且没有意识到影响可能会导致问题。添加了一种替代方法来保留被测试属性的完整行为。 - Sam

2
我推荐使用这个很棒的模拟库:https://github.com/rgvlee/EntityFrameworkCore.Testing 然后你可以轻松地进行如下操作:
    var mockedDbContext = Create.MockedDbContextFor<TestDbContext>();

    mockedDbContext.Query<TestQuery>().AddRangeToReadOnlySource(expectedResult);

我知道已经有一段时间了,但您是否知道有任何适用于.NET 5的解决方案?我一直在使用这个库,它非常好用,但它没有.NET 5支持,并且似乎没有维护。 - pzaj
1
@pzaj - 看起来现在已经更新了。 - arhnee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接