【问题标题】:Tests using InMemoryDatabase and Identity columns, how to deal?使用 InMemoryDatabase 和 Identity 列进行测试,如何处理?
【发布时间】:2019-04-09 23:48:55
【问题描述】:

.Net Core 2.2 / EFC 2.2.3 / Pomelo.EntityFrameworkCore.MySql 2.2.0

假设您有一个名为 Colors 的表,其中包含一些预定义的数据。

public void Configure(EntityTypeBuilder<Color> builder)
{
    builder.ToTable("Colors");
    builder.HasKey(r => r.Id).UseMySqlIdentityColumn();
    builder.Property(r => r.Name).IsRequired().HasMaxLength(255);
    builder.Property(v => v.RGB).IsRequired().HasMaxLength(7);
    builder.HasData(GetSeed());
}
private ICollection<Color> GetSeed()
{
    return new List<Color>()
    {
        new Color(){Id=1, Name="Black", RGB="#000"},
        new Color(){Id=2, Name="White", RGB="#fff"},
    }
}

我的一项测试是测试 CreateColorCommandHandler。很直接

var Context = CBERPContextFactory.Create();
var query = new CreateColorCommandHandler(Context);

var command = new CreateColorCommand();
command.Name= "Random color";
command.RGB = "#001122";

var colorId = await query.Handle(command, CancellationToken.None);

//Assert
Assert.IsInstanceOf<long>(colorId);
Assert.NotZero(colorId);

var cor = Context.Colors.Where(p => p.Id == colorId).SingleOrDefault();
Assert.NotNull(cor);
Assert.AreEqual(command.Name, cor.Name);
Assert.AreEqual(command.RGB, cor.RGB);

CBERPContextFactory.Destroy(Context);

//>>> Handle simply add a new entity without informing ID

处理方法

public async Task<long> Handle(CreateColorCommand request, CancellationToken cancellationToken)
{
    var entity = new Color
    {
        Name = request.Name,
        RGB = request.RGB,
    };

    _context.Colors.Add(entity);

    await _context.SaveChangesAsync(cancellationToken);

    return entity.Id;
}

当我运行这个测试时,我得到了错误An item with the same key has already been added. Key: 1。这意味着 InMemoryDatabase 没有自动增量功能。

我是不是写错了测试?

如何测试这样的案例?我想确保命令正常。

可能我在这里遗漏了一些非常基本的规则。

【问题讨论】:

  • 你能显示Handle方法吗?您如何将新颜色保存到数据库中?
  • 请注意,如果其中一个断言失败,CBERPContextFactory.Destroy(Context); 将不会被执行。
  • @Fabio,编辑显示手柄

标签: c# entity-framework unit-testing testing


【解决方案1】:

我认为问题出在以下行:

var Context = CBERPContextFactory.Create();

可能是您在多个测试中使用相同的上下文实例。根据Testing with InMemory 文档:

每个测试方法都指定一个唯一的数据库名称,这意味着每个方法都有自己的 InMemory 数据库。

因此,请确保您的每个测试方法都有不同的上下文实例。

如果仍然不起作用,请尝试手动设置标识键值,因为 InMemory 数据库可能不支持自动增量。

【讨论】:

  • 是的,我确信这个名字是独一无二的。对于每个Create() 调用,我都会生成一个新的 GUID 并将其设置为名称:builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
  • 尝试手动设置身份密钥,因为 InMemory 可能不支持自动递增。请告诉我结果。
  • 是的,这行得通,但这样做没有错吗?我再问一句:测试数据库可以有种子吗?
  • 在那个例子中,我已经在数据库中有 ID 为 1 和 2 的颜色,对吧?如果我使用其他预定义的颜色添加其他迁移,我的测试将中断,但它不应该。我说的对吗?
  • @lcssanches 1) Let me ask something else: do the test database can have seeds?- 是的,应该有! 2)是的,它会
【解决方案2】:

InMemoryDatabase 还没有所有功能,而 AUTO INCREMENT 是需要改进的功能之一:https://github.com/aspnet/EntityFrameworkCore/issues/6872

不是我想要的答案,而是目前有效的答案:在测试前清除所有种子。

    private static void Clear(this DbContext context)
    {
        var properties = context.GetType().GetProperties();

        foreach (var property in properties)
        {
            var setType = property.PropertyType;
            bool isDbSet = setType.IsGenericType && (typeof(DbSet<>).IsAssignableFrom(setType.GetGenericTypeDefinition()));
            if (!isDbSet) continue;
            dynamic dbSet = property.GetValue(context, null);
            dbSet.RemoveRange(dbSet);
        }
        context.SaveChanges();
    }

【讨论】:

    猜你喜欢
    • 2021-05-06
    • 1970-01-01
    • 2021-12-09
    • 2021-04-06
    • 1970-01-01
    • 2022-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多