EF 6伪造数据库上下文,可以';找不到实体

本文关键字:找不到 实体 可以 伪造 数据库 上下文 EF | 更新日期: 2023-09-27 18:27:53

我正在用单元测试覆盖我们的一些服务类,我已经使用NSubstitute隔离/伪造了dbcontext(遵循本指南)。我已经完成了一些测试并正在工作,一切似乎都很好,但现在我找不到我添加到上下文中的实体。

测试代码非常简单:

[Fact]
public void CreateStore_GivenAccount_AccountIsAssignedTheStore()
{
    const int accountId = 10;
    var account = new Account {Id = accountId};
    var fakeContext = new FakeContextBuilder()
        .WithAccounts(account)
        .Build();
    var service = new Service(fakeContext);
    const int someProperty = 0;
    const string someOtherProperty = "blabla";
    service.CreateStore(accountId, someProperty, someOtherProperty);
    var storeWasAdded = account.Stores
        .Any(store =>
            store.SomeProperty == someProperty &&
            store.SomeOtherProperty == someOtherProperty);
    Assert.True(storeWasAdded);
}

FakeContextBuilder是我为设置上下文而创建的助手类(类似于其他实体的方法):

public class FakeContextBuilder
{
    private DbSet<Account> _accountTable;
    private static DbSet<TEntity> SetUpFakeTable<TEntity>(params TEntity[] entities) where TEntity : class
    {
        var fakeTable = Substitute.For<DbSet<TEntity>, IQueryable<TEntity>>() as IQueryable<TEntity>;
        var table = entities.AsQueryable();
        fakeTable.Provider.Returns(table.Provider);
        fakeTable.Expression.Returns(table.Expression);
        fakeTable.ElementType.Returns(table.ElementType);
        fakeTable.GetEnumerator().Returns(table.GetEnumerator());
        return (DbSet<TEntity>) fakeTable;
    }
    public Context Build()
    {
        var context = Substitute.For<Context>();
        context.Accounts.Returns(_accountTable);
        return context;
    }
    public FakeContextBuilder WithAccounts(params Account[] accounts)
    {
        _accountTable = SetUpFakeTable(accounts);
        return this;
    }
}

服务方式:

public void CreateStore(int accountID, int someProperty, string someOtherProperty)
{
    var account = _context.Accounts.Find(accountID);
    account.Stores.Add(new Store(someProperty, someOtherProperty));
}

Accounts.Find()行中,我得到的是null,而不是预期的帐户实例。如果我添加一个断点并查看上下文,我会看到Accounts上的"枚举结果"不会产生任何结果,但我可以看到在非公共成员中正确设置了提供程序和枚举器等。伪上下文生成器在其他测试中也能很好地工作,所以我猜测这与Find()方法有关。

编辑:我现在已经确认Find()方法是罪魁祸首,因为在执行以下操作时测试通过了:

var account = _context.Accounts.Single(act => act.Id == 10);

我仍然想使用Find()进行缓存等等。这可以在测试代码中进行配置吗?我不想把生产代码搞砸,因为这实际上是一个简单的操作。

EF 6伪造数据库上下文,可以';找不到实体

我已经解决了这个问题。这可能不是有史以来最巧妙的解决方案,但它似乎起到了作用,我(至少目前)看不出它会成为以后的维护麻烦

我通过创建DbSet<T>的子类实现了这一点,我很有想象力地将其命名为DbSetWithFind<T>

public class DbSetWithFind<TEntity> : DbSet<TEntity> where TEntity : class
{
    private readonly IQueryable<TEntity> _dataSource;
    public DbSetWithFind(IQueryable<TEntity> dataSource)
    {
        _dataSource = dataSource;
    }
    public sealed override TEntity Find(params object[] keyValues) // sealed override prevents EF from "ruining" it.
    {
        var keyProperties = typeof (TEntity).GetProperties()
            .Where(property => property.IsDefined(typeof (KeyAttribute), true));
        return _dataSource.SingleOrDefault(entity =>
            keyProperties
                .Select(property => property.GetValue(entity))
                .Intersect(keyValues)
                .Any());
    }
}

然后我修改了Substitute.For()调用以使用子类,该子类包含Find()的自定义实现。

private static DbSet<TEntity> SetUpFakeTable<TEntity>(params TEntity[] entities) where TEntity : class
{
    var dataSource = entities.AsQueryable();
    var fakeDbSet = Substitute.For<DbSetWithFind<TEntity>, IQueryable<TEntity>>(dataSource); // changed type and added constructor params
    var fakeTable = (IQueryable<TEntity>) fakeDbSet;
    fakeTable.Provider.Returns(dataSource.Provider);
    fakeTable.Expression.Returns(dataSource.Expression);
    fakeTable.ElementType.Returns(dataSource.ElementType);
    fakeTable.GetEnumerator().Returns(dataSource.GetEnumerator());
    return (DbSet<TEntity>) fakeTable;
}