通过利用Testing with async queries section of the Testing with a Mocking Framework article on MSDN,我已经能够创建许多成功通过测试。
这是我的测试代码,它使用NSubstitute进行模拟:
var dummyQueryable = locations.AsQueryable();
var mock = Substitute.For<DbSet<Location>, IDbAsyncEnumerable<Location>, IQueryable<Location>>();
((IDbAsyncEnumerable<Location>)mock).GetAsyncEnumerator().Returns(new TestDbAsyncEnumerator<Location>(dummyQueryable.GetEnumerator()));
((IQueryable<Location>)mock).Provider.Returns(new TestDbAsyncQueryProvider<Location>(dummyQueryable.Provider));
((IQueryable<Location>)mock).Expression.Returns(dummyQueryable.Expression);
((IQueryable<Location>)mock).ElementType.Returns(dummyQueryable.ElementType);
((IQueryable<Location>)mock).GetEnumerator().Returns(dummyQueryable.GetEnumerator());
sut.DataContext.Locations = mock;
var result = await sut.Index();
result.Should().BeView();
sut.Index()
做得不多,但会进行以下查询:
await DataContext.Locations
.GroupBy(l => l.Area)
.ToListAsync());
这在我向查询添加投影之前一直正常工作:
await DataContext.Locations
.GroupBy(l => l.Area)
.Select(l => new LocationsIndexVM{ Area = l.Key }) // added projection
.ToListAsync());
导致此异常:
System.InvalidOperationException
The source IQueryable doesn't implement IDbAsyncEnumerable<LocationsIndexVM>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
at System.Data.Entity.QueryableExtensions.AsDbAsyncEnumerable(IQueryable`1 source)
at System.Data.Entity.QueryableExtensions.ToListAsync(IQueryable`1 source)
at Example.Web.Controllers.HomeController.<Index>d__0.MoveNext() in HomeController.cs: line 25
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Example.Test.Web.Controllers.HomeControllerShould.<TempTest>d__4.MoveNext() in HomeControllerShould.cs: line 71
更新:我已经uploaded a small, simple solution重现了这个问题。
任何人都可以提供一个示例,说明单元测试async
并包含.Select()
投影的查询所需的内容吗?
答案 0 :(得分:16)
所以我做了一些挖掘,问题与TestDbAsyncEnumerable<T>
暴露IQueryProvider
的方式有关。关于推理的最佳猜测如下,以及下面的解决方案。
TestDbAsyncEnumerable<T>
继承自EnumerableQuery<T>
,后者继承自IQueryable<T>
,并显式实现此接口的Provider
属性:
IQueryProvider IQueryable.Provider { get ... }
鉴于它是明确实现的,我假设LINQ内部在尝试获取Provider
之前显式地转换了类型:
((IQueryable<T>)source).Provider.CreateQuery(...);
我手边没有源代码(并且不会费心寻找),但我相信类型绑定规则对于显式实现是不同的;实质上,Provider
上的TestDbAsyncEnumerable<T>
属性不被视为IQueryable<T>.Provider
的实现,因为明确的属性存在于链的后面,因此永远不会返回TestDbAsyncQueryProvider<T>
。
解决此问题的方法是让TestDbAsyncEnumerable<T>
也继承IQueryable<T>
并明确实施Provider
属性,如下所示(根据MSDN article you linked进行调整):
public class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression) : base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}