如何在最小起订量的单元测试中模拟 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()
得到预期结果:
您可以更进一步,只需设置 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()));
});
}
我正在尝试在 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()
得到预期结果:
您可以更进一步,只需设置 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()));
});
}