2014-09-24 73 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; 
          });