Moq тестирование LINQ, где запросы

Я использую EF 4.1 для создания модели домена. У меня есть класс задач с методом Validate (string userCode), и в нем я хочу убедиться, что код пользователя соответствует действительному пользователю в базе данных, поэтому:

public static bool Validate(string userCode)
{
    IDbSet<User> users = db.Set<User>();
    var results = from u in users
              where u.UserCode.Equals(userCode)
              select u;
    return results.FirstOrDefault() != null;
}

Я могу использовать Moq для того чтобы издеваться IDbSet никакая проблема. Но нарвался на неприятности с тем где звонят:

User user = new User { UserCode = "abc" };
IList<User> list = new List<User> { user };
var users = new Mock<IDbSet<User>>();
users.Setup(x => x.Where(It.IsAny<Expression<Func<User, bool>>>())).Returns(list.AsQueryable);

Initialization method JLTi.iRIS3.Tests.TaskTest.SetUp threw exception.
System.NotSupportedException: System.NotSupportedException: Expression 
references a method that does not belong to the mocked object:
x => x.Where<User>(It.IsAny<Expression`1>()).

кроме создания уровня косвенности (например, с помощью ServiceLocator для получения объекта, который запускает LINQ, а затем издевается над этим методом), я не могу придумать, как еще проверьте это, но я хочу убедиться, что нет никакого способа, прежде чем я введу другой слой. И я вижу, что такие запросы LINQ будут нужны довольно часто, поэтому объекты службы могут быстро выйти из-под контроля.

может ли какая-то душа помочь? Спасибо!

4 ответов


как я знаю, Moq может настраивать только виртуальные методы самого издевающегося объекта, но вы пытаетесь настроить метод расширений (статический) - нет! Эти методы абсолютно выходят за пределы вашей области видимости.

кроме того, этот код трудно проверить и требует слишком много инициализации, чтобы его можно было проверить. Используйте этот код:

internal virtual IQueryable<User> GetUserSet()
{
    return db.Set<User>();
} 

public bool Validate(string userCode)
{
    IQueryable<User> users = GetUserSet();
    var results = from u in users
                  where u.UserCode.Equals(userCode)
                  select u;
    return results.FirstOrDefault() != null;
}

вам просто нужно настроить GetUserSet для возврата списка. Такое тестирование имеет некоторые основные проблемы:

  • вы не тестировать реальную реализацию - в случае насмешливых наборов EF глупый подход, потому что, как только вы это сделаете, вы измените linq-to-entities на linq-to-objects. Эти два совершенно разные, и linq-to-entities - это только небольшое подмножество linq-to-objects = ваши модульные тесты могут проходить с linq-to-objects, но ваш код не будет выполнен во время выполнения.
  • как только вы используете этот подход, вы не можете использовать Include потому что include зависит от DbQuery / DbSet. Снова вам нужен интеграционный тест для использования он.
  • это не проверяет, что ваша ленивая загрузка работает

лучший подход-удаление ваших запросов linq из Validate метод - просто вызовите их как другой виртуальный метод объекта. Модульный тест ваш Validate метод с издевательскими методами запросов и использовать интеграционные тесты для тестирования самих запросов.


есть статья о MSDN, освещающая, как издеваться с помощью moq: Суть его заключается в представлении операций linq to entities с linq to objects.

var mockSet = new Mock<DbSet<Blog>>(); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

Как указывает Ладислав, у этого есть недостатки, поскольку Linq to Objects просто отличается от Linq to Entities, поэтому это может привести к ложным срабатываниям. Но теперь, будучи статьей MSDN, она указывает, что это, по крайней мере, возможно и, возможно, рекомендуется в некоторых случаях?

одно дело, что может измениться, так как исходные ответы на этот пост заключаются в том, что команда Entity Framework открыла области Entity Framework в EF 6.0, чтобы облегчить издевательство над ее иннерами.


хотя я этого не пробовал, потому что IDBSet осуществляет IEnumerable возможно, вам придется издеваться над методом перечислителя, чтобы операторы linq забрали ваш список пользователей. Вы на самом деле не хотите издеваться над linq, но по внешнему виду вашего кода Вы хотите проверить, находите ли вы правильного пользователя на основе UserCode мне кажется, что это действует тестовый блок.

 var user = new User { UserCode = "abc" };
 var list = new List<User> { user };
 var users = new Mock<IDbSet<User>>();
 users.Setup(x => x.GetEnumerator()).Returns(list.GetEnumerator());

вы можете получить конфликт с неуниверсальный вариант GetEnumerator но это может помочь вам на право дорожка. Затем вы должны поместить насмешливый объект в контекст данных, который зависит от другого кода, который мы не видим.


мне было проще просто написать заглушку:

internal class FakeDbSet<T> : IDbSet<T>where T : class
{
    readonly HashSet<T> _data;
    readonly IQueryable _query;

    public FakeDbSet()
    {
        _data = new HashSet<T>();
        _query = _data.AsQueryable();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet<T> and override Find");
    }

    public T Add(T item)
    {
        _data.Add(item);
        return item;
    }

    public T Remove(T item)
    {
        _data.Remove(item);
        return item;
    }

    public T Attach(T item)
    {
        _data.Add(item);
        return item;
    }

    public void Detach(T item)
    {
        _data.Remove(item);
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _query.Provider; }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.GetEnumerator();
    }


    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public ObservableCollection<T> Local
    {
        get
        {

            return new ObservableCollection<T>(_data);
        }
    }