Подтвердить что ты не робот

Moq-тестирование LINQ Где запросы

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

Помогла ли какая-нибудь душа? Спасибо!

4b9b3361

Ответ 1

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

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

Ответ 2

В 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, чтобы упростить их издевку внутри.

Ответ 3

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

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

Ответ 4

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

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