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()进行缓存等等。这可以在测试代码中进行配置吗?我不想把生产代码搞砸,因为这实际上是一个简单的操作。
我已经解决了这个问题。这可能不是有史以来最巧妙的解决方案,但它似乎起到了作用,我(至少目前)看不出它会成为以后的维护麻烦
我通过创建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;
}