[英]EF 6 fake db context, can't find the entity
我正在用單元測試介紹一些服務類,並且已經設法使用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
是我為設置上下文而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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.