EF 6假数据库上下文,找不到实体

时间:2015-12-22 12:37:44

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

我正在使用单元测试来覆盖我们的一些服务类,并且我已经设法使用NSubstitute隔离/伪造dbcontext(遵循this指南)。我做了一些测试并且正在工作,事情似乎没问题,但现在我找不到我添加到上下文中的实体。

测试代码非常简单:

[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而不是预期的帐户实例。如果我添加一个断点并查看上下文,我看到在“帐户”上“枚举结果”没有产生结果,但我可以看到提供者和枚举器等在非公共成员中正确设置。伪上下文构建器在其他测试中也可以正常工作,所以我的猜测是这与Find()方法有关。

编辑:我现在已经确认Find()方法是罪魁祸首,因为测试通过了这样做:

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

我仍然希望将Find()用于缓存目的等等。这可以在测试代码中以某种方式配置吗?不愿意为此搞乱生产代码,因为这真的是一个简单的操作。

1 个答案:

答案 0 :(得分:0)

我已经解决了这个问题。它可能不是有史以来最巧妙的解决方案,但似乎可以做到这一点,而且我现在看不到(至少目前)这将是一种维护滋扰。

我通过创建一个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;
}