Dan Solovay
Dan Solovay

Reputation: 3164

Unit Testing IQueryable operations

While writing a unit test for a Sitecore 7 search class, following the pattern established here (https://github.com/Sitecore/sitecore-seven-unittest-example/blob/master/ExampleFixture.cs), I am running into an issue where in order to make my tests pass I need to add AsEnumerable() to the Sitecore LINQ expression.

Here is my unit test:

[Test]
public void Provider_WhenCalled_MatchesPath()
{
  //arrange
  var index = MakeSubstituteIndex(new List<EventPageSearchItem>
  {
   new EventPageSearchItem(){Path = "/good/path/folder1/item1", TemplateId = _eventTemplateId },
   new EventPageSearchItem{Path="/good/path/folder2/item2", TemplateId = _eventTemplateId},
   new EventPageSearchItem{Path="/bad/path/folder2/item2", TemplateId = _eventTemplateId}
            });
  //act
  var provider = new UpcomingEventProvider(index);
  var events = provider.GetEvents(3, "/good/path");

  //assert
  Assert.That(events.ToList(), Has.Count.EqualTo(2));
}

private static ISearchIndex MakeSubstituteIndex(List<EventPageSearchItem> itemsToReturn)
    {
        ISearchIndex index = Substitute.For<ISearchIndex>();
        _eventTemplateId = MyProject.Library.IEvent_PageConstants.TemplateId;

        SimpleFakeRepo<EventPageSearchItem> repo = new SimpleFakeRepo<EventPageSearchItem>(itemsToReturn);
        index.CreateSearchContext().GetQueryable<EventPageSearchItem>().Returns(repo);
        return index;
    }


public class SimpleFakeRepo<T> : EnumerableQuery<T>
{
    public SimpleFakeRepo(IEnumerable<T> enumerable)
        : base(enumerable)
    {}
}

And this is my logic under test:

public class EventPageSearchItem: SearchResultItem
{
}

public interface IUpcomingEventProvider
{
    IEnumerable<EventPageSearchItem> GetEvents(int numberOfEvents, string rootItemPath);
}

public class UpcomingEventProvider : IUpcomingEventProvider
{
    private readonly ISearchIndex _searchIndex;

    public UpcomingEventProvider(ISearchIndex searchIndex)
    {
        _searchIndex = searchIndex;
    }

    public IEnumerable<EventPageSearchItem> GetEvents(int numberOfEvents, string rootItemPath)
    {
        var ctx = _searchIndex.CreateSearchContext();
        var queryable = ctx.GetQueryable<EventPageSearchItem>();
        return queryable
            .Where(item => 
                item.TemplateId== MyProject.Library.IEvent_PageConstants.TemplateId 
                && item.Path.StartsWith(rootItemPath)).Take(numberOfEvents);
    }
}
}

This is failing with this stack trace:

System.ArgumentNullException : Value cannot be null.
Parameter name: arguments
   at System.Linq.Expressions.Expression.RequiresCanRead(Expression expression, String paramName)
   at System.Linq.Expressions.Expression.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arg, ParameterInfo pi)
   at System.Linq.Expressions.Expression.ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ReadOnlyCollection`1& arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
   at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
   at MyProject.UnitTests.ComponentUtilityTests.UpcomingEventProvider.GetEvents(Int32 numberOfEvents, String rootItemPath)  
   at MyProject.UnitTests.ComponentUtilityTests.UpcomingEventProviderTests.    System.ArgumentNullException : Value cannot be null.

If I modify the Linq expression to include AsEnumerable(), everything passes, but this will prevent the Linq from generating an expression tree for the index provider.

return queryable.AsEnumerable().Where( etc. )

Upvotes: 3

Views: 1681

Answers (1)

Ben Golden
Ben Golden

Reputation: 1580

I'm not sure about the differences between your mocking frameworks, but I notice that Stephen's code explicitly sets up a fake return value for index.CreateSearchContext(). Your code does not as far as I can see. That is the only notable difference I can see between your code and his.

A similar error message here also indicates that this error message can come from a lack of recursive mock support: Rhino Mock Entity Framework using UnitofWork Pattern not working

Upvotes: 1

Related Questions