Lazy Exceptions for Fake LINQ Queries

Wednesday, June 22, 2011

If you want to simulate an exception from an IQueryable data source, be careful about when the exception is thrown. As an example, let's use the following interface.

public interface IContext
{
    IQueryable<Person> People { get; set; }
}

To simulate an IllegalOperationException from the data source, you might setup a mock with the following code.

var mockContext = new Mock<IContext>();
mockContext.SetupGet(c => c.People)
           .Throws<InvalidOperationException>();

But, the mock will throw the exception as soon as something touches the People property. With a real data source, the exception won't happen until the query executes. Think about the misbehavior the mock would create with the following code.

var query = context.People
                   .Where(p => p.BirthDate.Year > 2000);

try
{
    foreach (var p in query)
    {
        Console.WriteLine(p.Name);
    }
}
catch (InvalidOperationException ex)
{
    // ...
}

To exercise the catch logic, you'll need to defer the exception until the query executes.  You might think this is as easy as waiting till GetEnumerator is invoked to throw the exception.

var queryable = new Mock<IQueryable<Person>>();
queryable.Setup(q => q.GetEnumerator())
         .Throws<InvalidOperationException>();
                       
var mockContext = new Mock<IContext>();
mockContext.SetupGet(c => c.People)
           .Returns(queryable.Object);

Unfortunately, the above code doesn't work. To have LINQ operators like Where, Select, and OrderBy work properly requires more plumbing behind the IQueryable object. The above code fails with the first call to Where.

Here is a hand rolled class that works on my machine.

public class ThrowingQueryable<T, TException> 
    : IQueryable<T> where TException : Exception, new()
{
    public IEnumerator<T> GetEnumerator() {
        throw new TException();            
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }

    public Expression Expression {
        get { return Expression.Constant(this); }
    }

    public Type ElementType {
        get { return typeof(T); }
    }

    public IQueryProvider Provider {
        get { return new ThrowingProvider(this); }
    }

    class ThrowingProvider : IQueryProvider            
    {
        private readonly IQueryable<T> _source;

        public ThrowingProvider(IQueryable<T> source) {
            _source = source;
        }

        public IQueryable CreateQuery(Expression expression) {
            return CreateQuery<T>(expression);
        }

        public IQueryable<T> CreateQuery<T>(Expression expression) {
            return _source as IQueryable<T>;
        }

        public object Execute(Expression expression) {
            return null;
        }

        public TResult Execute<TResult>(Expression expression) {
            return default(TResult);
        }
    }
}

You can use it like so:

var mockContext = new Mock<IContext>();
mockContext
    .SetupGet(c => c.People)
    .Returns(new ThrowingQueryable<Person, InvalidOperationException>());

Comments
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!