2014-09-24 131 views
13

我已经能够用Moq使用此link从实体框架中模拟DbSet的。如何Moq实体框架SqlQuery调用

但是,我现在想知道我可以如何模拟对SqlQuery的调用。不知道这是可能的还是因为它依赖于模拟的数据库上下文知道什么“查询”被调用。

下面是我想嘲笑的。

var myObjects = DbContext.Database 
    .SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", "some_value") 
    .ToList(); 

我目前还没有尝试过任何东西,因为不知道如何开始嘲笑这个例子。

DbSet的嘲弄低于并再次重申,我可以正确嘲笑返回MyObjectDbSet的,但现在我试图嘲弄返回的MyObject列表SQLQuery对的。

var dbContext = new Mock<MyDbContext>(); 
dbContext.Setup(m => m.MyObjects).Returns(mockObjects.Object); 

dbContext.Setup(m => m.Database.SqlQuery... something along these lines 

回答

14

Database.SqlQuery<T>未被标记为虚拟,但Set<T>.SqlQuery被标记为虚拟。

基于Database.SqlQuery<T>文档

此查询的结果永远不会被即使 类型对象的返回是一个实体类型的上下文跟踪。使用'SqlQuery(String, Object[])'方法返回由 上下文跟踪的实体。

Set<T>.SqlQuery文档

默认情况下,返回由上下文跟踪的实体;这可以通过在返回的DbRawSqlQuery上调用AsNoTracking来更改 。

那么Database.SqlQuery<T>(String, Object[])应相当于与Set<T>.SqlQuery(String, Object[]).AsNoTracking()(仅当T是EF实体,而不是一个DTO/VM)。

所以,如果你可以替换的实施分为:

var myObjects = DbContext 
    .Set<MyObject>() 
    .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value") 
    .AsNoTracking() 
    .ToList(); 

你可以嘲笑它遵循

var list = new[] 
{ 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "another_value" } 
}; 

var setMock = new Mock<DbSet<MyObject>>(); 
setMock.Setup(m => m.SqlQuery(It.IsAny<string>(), It.IsAny<object[]>())) 
    .Returns<string, object[]>((sql, param) => 
    { 
     // Filters by property. 
     var filteredList = param.Length == 1 
      ? list.Where(x => x.Property == param[0] as string) 
      : list; 
     var sqlQueryMock = new Mock<DbSqlQuery<MyObject>>(); 
     sqlQueryMock.Setup(m => m.AsNoTracking()) 
      .Returns(sqlQueryMock.Object); 
     sqlQueryMock.Setup(m => m.GetEnumerator()) 
      .Returns(filteredList.GetEnumerator()); 
     return sqlQueryMock.Object; 
    }); 

var contextMock = new Mock<MyDbContext>(); 
contextMock.Setup(m => m.Set<MyObject>()).Returns(setMock.Object); 
+0

这个工作对我来说太棒了。对我而言,这比将上述接受的答案中的查询逻辑抽象为帮助者更可取。 – JamesWampler 2015-10-01 22:16:14

8

Database财产和SqlQuery方法没有被标记为virtual所以他们can't be mocked(使用起订量,你可以使用一个different library能考虑到这一点,但可能比你想的惯性)。

你需要使用某种抽象的来解决这个问题,比如通过一个辅助类包装数据库的整个查询:

public interface IQueryHelper 
{ 
    IList<MyObject> DoYourQuery(string value); 
} 

public class QueryHelper : IQueryHelper 
{ 
    readonly MyDbContext myDbContext; 

    public QueryHelper(MyDbContext myDbContext) 
    { 
     this.myDbContext = myDbContext; 
    } 

    public IList<MyObject> DoYourQuery(string value) 
    { 
     return myDbContext.Database.SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", value).ToList(); 
    } 
} 

现在正在测试的方法变为:

public void YourMethod() 
{ 
    var myObjects = queryHelper.DoYourQuery("some_value"); 
} 

然后,你会注入IQueryHelper在你正在测试的类的构造函数和嘲笑。

您将错过DoYourQuery的测试范围,但现在查询的是so simple there are obviously no deficiencies

5

您可以添加一个虚拟方法到数据库背景下,我们可以覆盖在单元测试:

public partial class MyDatabaseContext : DbContext 
{ 
    /// <summary> 
    /// Allows you to override queries that use the Database property 
    /// </summary> 
    public virtual List<T> SqlQueryVirtual<T>(string query) 
    { 
     return this.Database.SqlQuery<T>(query).ToList(); 
    } 
} 
1

任何人都应该碰到这个。我用一些方法解决了这个问题。解决这个问题的另一种方法。

  1. 我的上下文通过一个接口被抽象出来。我只需要几个方法:

    public interface IDatabaseContext 
    { 
        DbSet<T> Set<T>() where T : class; 
        DbEntityEntry<T> Entry<T>(T entity) where T : class; 
        int SaveChanges(); 
        Task<int> SaveChangesAsync(); 
        void AddOrUpdateEntity<TEntity>(params TEntity[] entities) where TEntity : class; 
    

    }

  2. 我所有的数据库访问是通过异步方法。当试图嘲笑它时会引发一系列新问题。幸运的是 - 它已被回答here.您得到的异常与IDbAsyncEnumerable的缺失模拟有关。使用提供的解决方案 - 我只是扩展了一点,以便我有一个帮助器来返回一个模拟所有预期属性的Mock>对象。

    public static Mock<DbSqlQuery<TEntity>> CreateDbSqlQuery<TEntity>(IList<TEntity> data) 
        where TEntity : class, new() 
    { 
        var source = data.AsQueryable(); 
        var mock = new Mock<DbSqlQuery<TEntity>>() {CallBase = true}; 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator()); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider)); 
        mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator())); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity()); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; }); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; }); 
        return mock; 
    } 
    
  3. 最后 - 用@Yulium钱德拉提供的解决方案 - 我的原始SQL与嘲弄方面的测试看起来像:

    public Mock<DbSet<TestModel>> MockDbSet { get; } 
    
        .... 
    
        MockDbSet.Setup(x => x.SqlQuery(It.IsAny<string>)) 
          .Returns<string,object[]> 
          ((sql, param) => 
          { 
           var sqlQueryMock = MockHelper.CreateDbSqlQuery(Models); 
    
           sqlQueryMock.Setup(x => x.AsNoTracking()) 
            .Returns(sqlQueryMock.Object); 
    
           return sqlQueryMock.Object; 
          });