Moq тестирование LINQ Где запросы - PullRequest
15 голосов
/ 11 июля 2011

Я использую EF 4.1 для построения модели домена.У меня есть класс Task с методом 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нет проблем.Но столкнулся с проблемой вызова Where:

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 ]

20 голосов
/ 01 апреля 2014

На MSDN есть статья, в которой рассказывается, как использовать moq : Суть этого в том, чтобы представлять linq для операций с сущностями с linq для объектов.

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, чтобы было проще высмеивать ее внутренности.

10 голосов
/ 11 июля 2011

Хотя я не пробовал этого, потому что 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, но это может помочь вам на правильном пути. Затем вы должны поместить макет объекта в контекст данных, который зависит от другого кода, который мы не видим.

9 голосов
/ 11 июля 2011

Насколько я знаю, 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-entity на linq-to-объекты.Эти два абсолютно разные, и linq-to-entity - это лишь небольшое подмножество linq-to-objects = ваши модульные тесты могут проходить с linq-to-objects, но ваш код потерпит неудачу во время выполнения.
  • Когда вы используете этот подход, вы не можете использовать Include, потому что include зависит от DbQuery / DbSet.Опять же, вам нужен интеграционный тест, чтобы использовать его.
  • Это не проверяет, что ваша ленивая загрузка работает

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

4 голосов
/ 12 марта 2012

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

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);
        }
    }
...