2015-12-22 32 views
0

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

测试代码是非常简单的:

[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是一个辅助I类用于建立上下文(对于其它实体的类似方法)制备:

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()方法用于高速缓存的目的等。可以在测试代码中以某种方式进行配置吗?会讨厌为此弄乱生产代码,因为这实际上是一个简单的操作。

+0

这似乎是问题的事实,DbSet .Find()是'virtual',所以NSubstitute是使用空的实现覆盖它而产生。将进一步调查。 – kai

回答

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; 
} 
相关问题