我的源代码
var results = searchContext.GetQueryable<Model>().Where(GetSearchPredicate()).GetResults();
private Expression<Func<Model, bool>> GetSearchPredicate()
{
Expression<Func<Model, bool>> filterSearch = x => true;
filterSearch = filterSearch.And(GetTemplateFilterPredicate());
return filterSearch;
}
这里GetTemplateFilterPredicate是另一个私有方法,它执行更多的过滤。
我的单元测试逻辑
[Theory]
[AutoData]
public void GetEvent_ReturnResult()
{
var Obj = new List<Model>
{
new Model
{
Name="testname123",
TemplateName="Analysis",
Id = "123",
Description="TestDescription123",
}
}.AsEnumerable();
var searchContext = Substitute.For<IProviderSearchContext>();
var queryable = new LuceneProviderQueryableStub<Model>(Obj);
searchContext.GetQueryable<Model>().Returns(queryable);
var mockRepo = new Mock<AnalysisService>();
mockRepo.Protected()
.Setup<IProviderSearchContext>("GetSearchContext")
.Returns(searchContext);
// Act
var result = mockRepo.Object.Get("123");
//Assert
}
我的LuceneProviderQueryableStub
public class LuceneProviderQueryableStub<TElement> : IOrderedQueryable<TElement>, IOrderedQueryable, IQueryProvider, IQueryable<TElement>
{
private readonly EnumerableQuery<TElement> innerQueryable;
public Type ElementType { get { return ((IQueryable)innerQueryable).ElementType; } }
public Expression Expression { get { return ((IQueryable)innerQueryable).Expression; } }
public IQueryProvider Provider { get { return this; } }
public LuceneProviderQueryableStub(IEnumerable<TElement> enumerable)
{
innerQueryable = new EnumerableQuery<TElement>(enumerable);
}
public LuceneProviderQueryableStub(Expression expression)
{
innerQueryable = new EnumerableQuery<TElement>(expression);
}
public IQueryable CreateQuery(Expression expression)
{
expression = new FilterCallsReplacer().Visit(expression);
return new LuceneProviderQueryableStub<TElement>((IEnumerable<TElement>)((IQueryProvider)innerQueryable).CreateQuery(expression));
}
public IQueryable<TElement1> CreateQuery<TElement1>(Expression expression)
{
expression = new FilterCallsReplacer().Visit(expression);
return (IQueryable<TElement1>)new LuceneProviderQueryableStub<TElement>((IEnumerable<TElement>)((IQueryProvider)innerQueryable).CreateQuery(expression));
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
var items = this.ToArray();
object results = new SearchResults<TElement>(items.Select(s => new SearchHit<TElement>(0, s)), items.Length);
return (TResult)results;
}
public IEnumerator<TElement> GetEnumerator()
{
return ((IEnumerable<TElement>)innerQueryable).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
internal class FilterCallsReplacer : ExpressionVisitor
{
private static readonly MethodInfo FilterMethod = typeof(QueryableExtensions)
.GetMethod(nameof(QueryableExtensions.Filter), BindingFlags.Static | BindingFlags.Public);
private static readonly MethodInfo WhereMethod = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(method => method.Name == nameof(Queryable.Where));
protected override Expression VisitMethodCall(MethodCallExpression node)
{
return IsFilterMethod(node)
? RewriteToWhere(node)
: base.VisitMethodCall(node);
}
private static bool IsFilterMethod(MethodCallExpression node)
{
return node.Method.IsGenericMethod && node.Method.GetGenericMethodDefinition() == FilterMethod;
}
private Expression RewriteToWhere(MethodCallExpression node)
{
var arguments = node.Arguments.ToArray();
var type = node.Method.GetGenericArguments().First();
var whereMethod = WhereMethod.MakeGenericMethod(type);
return Expression.Call(whereMethod, arguments);
}
}
我的期望是绕过查询并返回Mock数据。但是调试器在Where查询中命中了私有方法,这是不应该发生的。
我是单元测试的新手,不确定Where子句的存根有私有方法调用。
我们应该怎么写存根呢?
1条答案
按热度按时间uqjltbpv1#
您的问题源于
Expression<Func<T, bool>>
predicate 的行为和LINQ的执行模型。当您在Where()
子句中使用类似GetSearchPredicate()
的方法时,它不会立即“运行”,而是构建一个表达式树,稍后在实际枚举数据时对其进行计算。因此,当您使用LuceneProviderQueryableStub
剔除GetQueryable<Model>()
时,它仍然需要计算表达式的Where(GetSearchPredicate())
部分,这将调用私有方法。为了正确地进行单元测试,我们需要存根或模拟
Where
方法的行为,使其不计算GetSearchPredicate()
。有几种不同的方法可以实现这一点:**1.隔离逻辑:**重构方法,将取数逻辑和过滤逻辑分离。然后,您可以独立测试每个部分。例如,测试
GetSearchPredicate()
在给定的特定条件下返回正确的 predicate ,并测试您的数据检索方法在给定的特定 predicate 下正确地检索数据。**2.使用Mocking Framework绕过Where子句:**您使用的是NSubstitute,但模拟
Where()
等扩展方法更具挑战性。您可以考虑使用Moq,它支持mocking扩展方法,尽管有一些变通办法。**3.截断整个Where方法:**您可以更深入地使用截断并替换整个
Where
方法,但这很复杂,并且使测试不那么直观。让我们用Moq尝试第二种方法:
csharp
这个设置使用Moq创建一个
DbSet<Model>
的mock,它表示您的可查询数据。然后绕过Where
方法直接返回未过滤的数据。这样,GetSearchPredicate()
和它调用的私有方法就不会被执行。但是,请记住,这只是一种方法。单元测试的关键是隔离功能单元,因此从长远来看,重构方法可能是最主要和最清晰的。