2014-09-24 6 views
13

Я смог высмеять DbSet из структуры сущности с помощью Moq, используя это link.Как Moq Entity Framework SqlQuery вызывает

Однако теперь я хотел бы знать, как я мог высмеивать вызов SqlQuery. Не уверен, что это возможно или как он полагается на насмешливый контекст db, зная, что вызывает запрос.

Ниже я пытаюсь высмеять.

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

Я в настоящее время не пробовал ничего, так как не знал, как начать издеваться над этим примером.

насмешки в DbSet ниже и повторно итерацию, я могу правильно издеваться возвращая DbSet из MyObject «S, но теперь пытаюсь издеваться в SQLQuery, который возвращает список MyObject» с.

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 документация

По умолчанию, объекты, возвращаемые отслеживаются контекстом; это можно изменить , вызвав AsNoTracking на возвращаемом DbRawSqlQuery.

то 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

8

Database свойство и SqlQuery метод не помечаются как virtual поэтому они can't be mocked (используя Moq, вы можете использовать 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 Chandra - мое тестирование сырья 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; 
          }); 
    
Смежные вопросы