如何在最小起订量的单元测试中模拟 Nhibernate .ToListAsync()?

How to mock Nhibernate .ToListAsync() in Unit-test with moq?

我正在尝试在 ASP.NET 核心 MVC 应用程序中使用最小起订量创建单元测试。不幸的是,Nhibernate.ToListAsync() 不支持 Linq IQueryable 数据集并抛出 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,但如何模拟?

您需要指示 CreateQuery 使用表达式。只是 returning 模拟的可查询不会像您所看到的那样做任何事情。此外,CreateQuery 将需要 return 具有实现 INhQueryProvider 的提供程序的 IQueryable。问题是 Provider 属性 没有 setter 所以你不能在现有的可查询对象上设置它。

我解决类似问题的方法是创建我自己的序列,我可以在其中设置提供程序。

从创建实现 IQueryable<T>INhQueryProvider 的 类 开始;为简洁起见,我只实现了通过 OP 用例所需的内容。请注意,CreateQuery<T> return 是可查询的,提供者实现了 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() 得到预期结果:

Working example

您可以更进一步,只需设置 ISession 模拟以使用您的 IQueryable 实现,如果您不需要专门模拟查询提供程序,则不要模拟它。如果您知道我的意思,我通常不会嘲笑什么returns,这样就符合我的同行评审标准。

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