Как смоделировать Nhibernate .ToListAsyn c () в модульном тесте с moq? - PullRequest
1 голос
/ 24 апреля 2020

Я пытаюсь создать юнит-тесты в ASP. NET Core MVC приложении с moq. К сожалению, Nhibernate.ToListAsync() не поддерживается Linq IQueryable набором данных и throw System.NotSupportedException: 'Source Provider must be a INhQueryProvider'. В этом коде я высмеиваю INhQueryProvider, но этого недостаточно:

var entities = new List<RequestRole>
{
    new RequestRole()
    {
       Id = 0,
       RequestOperator = new RequestOperator() { Id = 1 }
    },
    new RequestRole()
    {
       Id = 1,
       RequestOperator = new RequestOperator() { Id = 2 }
     }
}
.AsQueryable();

// for ToListAsync Mock INhQueryProvider and set it into IQueryable
var queryableProviderMock = new Mock<INhQueryProvider>();
queryableProviderMock.Setup(x => x.ExecuteAsync<IEnumerable<RequestRole>>(It.IsAny<Expression>(), It.IsAny<CancellationToken>()))
                                 .ReturnsAsync(entities);

var queryableMock = new Mock<IQueryable<RequestRole>>();
queryableMock.Setup(x => x.Provider).Returns(queryableProviderMock.Object);
queryableMock.Setup(x => x.Expression).Returns(entities.Expression);
queryableMock.Setup(x => x.GetEnumerator()).Returns(entities.GetEnumerator());
queryableMock.Setup(x => x.ElementType).Returns(entities.ElementType);

// mock CreateQuery, without this Linq.Where throwing "System.NotSupportedException: 'Source Provider must be a INhQueryProvider'"
queryableProviderMock.As<INhQueryProvider>()
    .Setup(x => x.CreateQuery<RequestRole>(It.IsAny<Expression>()))
    .Returns(queryableMock.Object);

var session = new Mock<ISession>();
session.Setup(s => s.Query<RequestRole>()).Returns(queryableMock.Object);
var returns = session.Object.Query<RequestRole>();

// check work
var tolistasync = await returns
    .Where(x => x.Id != 0)
    .ToListAsync();

В этом случае Linq.Where условия не работают, потому что я установил тот же объект вместо фильтрованного. Похоже, я должен правильно издеваться INhQueryProvider.CreateQuery, но как?

1 Ответ

0 голосов
/ 25 апреля 2020

Вам нужно будет указать CreateQuery использовать выражение. Просто возвращение поддельного запроса не будет ничего делать, как вы видели. Кроме того, CreateQuery потребуется вернуть IQueryable с поставщиком, который реализует INhQueryProvider. Проблема в том, что свойство Provider не имеет установщика, поэтому вы не можете установить его для существующего запроса.

Способ, с помощью которого я решил подобную проблему, состоит в создании собственной последовательности, в которой я могу установите провайдера.

Начните с создания классов, которые реализуют IQueryable<T> и INhQueryProvider; для краткости я только реализую то, что требуется для передачи сценария использования OP. Обратите внимание, что CreateQuery<T> возвращает запрос с поставщиком, который реализует INhQueryProvider:

public class TestingQueryable<T> : IQueryable<T>
{
    private readonly IQueryable<T> _queryable;

    public TestingQueryable(IQueryable<T> queryable)
    {
        _queryable = queryable;     
        Provider = new TestingQueryProvider<T>(_queryable);
    }

    public Type ElementType => _queryable.ElementType;

    public Expression Expression =>  _queryable.Expression;

    public IQueryProvider Provider { get; }

    public IEnumerator<T> GetEnumerator()
    {
        return _queryable.GetEnumerator();
    }

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

public class TestingQueryProvider<T> : INhQueryProvider
{
    public TestingQueryProvider(IQueryable<T> source)
    {
        Source = source;
    }

    public IQueryable<T> Source { get; set; }

    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        return new TestingQueryable<TElement>(Source.Provider.CreateQuery<TElement>(expression));
    }

    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }

    public TResult Execute<TResult>(Expression expression)
    {
        return Source.Provider.Execute<TResult>(expression);
    }

    public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute<TResult>(expression));
    }

    public int ExecuteDml<T1>(QueryMode queryMode, Expression expression)
    {
        throw new NotImplementedException();
    }

    public Task<int> ExecuteDmlAsync<T1>(QueryMode queryMode, Expression expression, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public IFutureEnumerable<TResult> ExecuteFuture<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public IFutureValue<TResult> ExecuteFutureValue<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }

    public void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters)
    {
        throw new NotImplementedException();
    }
}

Обновите настройку поставщика запросов, чтобы использовать реализацию IQueryable:

queryProviderMock
        .Setup(x => x.CreateQuery<RequestRole>(It.IsAny<Expression>()))
        .Returns((Expression providedExpression) =>
        {           
            return new TestingQueryable<RequestRole>(queryable.Provider.CreateQuery<RequestRole>(providedExpression));
        });

Выполнить .Where(x => x.Id != 0).ToListAsync() и получите ожидаемый результат:

LINQPad result

Рабочий пример

Вы можете пойти дальше и просто настроить если вы используете ISession для использования вашей реализации IQueryable, покончите с издевательством над поставщиком запросов, если вам не нужно специально издеваться над ним. Я обычно не высмеиваю, что издевается, если вы понимаете, что я имею в виду, так что это будет соответствовать моим стандартам рецензирования.

[Test]
public async Task Test2()
{
    var requestRoles = new List<RequestRole>();
    requestRoles.Add(new RequestRole { Id = 0, RequestOperator = new RequestOperator { Id = 1 } });
    requestRoles.Add(new RequestRole { Id = 1, RequestOperator = new RequestOperator { Id = 2 } });

    var sessionMock = new Mock<ISession>();
    sessionMock.Setup(s => s.Query<RequestRole>()).Returns(new TestingQueryable<RequestRole>(requestRoles.AsQueryable()));
    var query = sessionMock.Object.Query<RequestRole>();

    var result = await query.Where(x => x.Id != 0).ToListAsync();

    Assert.Multiple(() =>
    {
        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result.Single(), Is.EqualTo(requestRoles.Last()));
    });
}
...