Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mock IRavenQueryable with a Where() expression appended

I'm trying to do some basic proof of concept type code for a new mvc3 project. We are using Moq with RavenDB.

Action:

public ActionResult Index(string id)
{
    var model = DocumentSession.Query<FinancialTransaction>()
        .Where(f => f.ResponsibleBusinessId == id);
    return View(model);
}

Test:

private readonly Fixture _fixture = new Fixture();

[Test]
public void Index_Action_Returns_List_Of_FinancialTransactions_For_Business([Random(0, 50, 5)]int numberOfTransactionsToCreate)
{
    // Arrange
    var session = new Mock<IDocumentSession>();
    var financialController = new FinancialController { DocumentSession = session.Object };

    var businessId = _fixture.CreateAnonymous<string>();
    var transactions = _fixture.Build<FinancialTransaction>()
        .With(f => f.ResponsibleBusinessId, businessId)
        .CreateMany(numberOfTransactionsToCreate);

    // Mock
    var ravenQueryableMock = new Mock<IRavenQueryable<FinancialTransaction>>();
    ravenQueryableMock.Setup(x => x.GetEnumerator()).Returns(transactions.GetEnumerator);
    ravenQueryableMock.Setup(x => x.Customize(It.IsAny<Action<Object>>()).GetEnumerator()).Returns(() => transactions.GetEnumerator());

    session.Setup(s => s.Query<FinancialTransaction>()).Returns(ravenQueryableMock.Object).Verifiable(); 

    // Act
    var actual = financialController.Index(businessId) as ViewResult;

    // Assert
    Assert.IsNotNull(actual);
    Assert.That(actual.Model, Is.InstanceOf<List<FinancialTransaction>>());

    var result = actual.Model as List<FinancialTransaction>;
    Assert.That(result.Count, Is.EqualTo(numberOfTransactionsToCreate));
    session.VerifyAll();
}

It would appear the problem is in the .Where(f => f.ResponsibleBusinessId == id). From the mocked IRavenQueryable, I'm returning a list of FinancialTransactions, so one would think the .Where() would filter based on that. But since it's IQueryable, I'm guessing it's trying to execute the expression all as one, when it's enumerating.

To verify, I changed the action's query to this:

var model = DocumentSession.Query<FinancialTransaction>()
    .ToList()
    .Where(f => f.ResponsibleBusinessId == id);

This does let the test pass, however, it's not ideal, as that means it's going to enumerate all the records, then filter them.

Is there any way to get Moq work with this?

like image 683
mandreko Avatar asked Apr 12 '12 19:04

mandreko


People also ask

How to mock the IQueryable extensions in Moq?

In order to mock the IQueryable extensions I do a little Moq magic and map the properties from my mocked object to the properties from my List. In order to accomplish “verifying” calls to the IQueryable I also piped in a callback for the Expression property.

Is there a way to mock up the data in imongoqueryable?

Finally, you find the answer on Stack Overflow. There are two ways to mock up the data, and both make the IMongoQueryable class accept IQueryable. Pretty slick.

What is mocking in Moq?

Mocking is a technique where a dependency to a class is swapped out (mocked) with an alternate/fake equivalent to the functionality so that the class can be tested in isolation from it’s dependencies. For these examples, I’ll be using the mocking framework Moq.

What is mocking extension methods in Java?

Mocking Extension Methods. Mocking. Mocking is a technique where a dependency to a class is swapped out (mocked) with an alternate/fake equivalent to the functionality so that the class can be tested in isolation from it’s dependencies.


1 Answers

As others have mentioned, if you can get by with the in-memory/embedded mode it is great for integration testing. But it isn't fast or easy enough for unit testing, in my opinion.

I found a blog post by Sam Ritchie that offers a 'fake' (wrapper around a standard LINQ IQueryable) for a IRavenQueryable for cases like this. His is slightly outdated, as newer versions of Raven (currently 2.5) offer a few extra methods on the IRavenQueryable interface. I currently don't use those new methods though (TransformWith, AddQueryInput, Spatial), so I just lazily left NotImplementedException in the code below, for now.

See Sam's post for the original code I based this on, and for usage examples.

public class FakeRavenQueryable<T> : IRavenQueryable<T> {
    private readonly IQueryable<T> source;

    public FakeRavenQueryable(IQueryable<T> source, RavenQueryStatistics stats = null) {
        this.source = source;
        this.QueryStatistics = stats;
    }

    public RavenQueryStatistics QueryStatistics { get; set; }

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

    public Expression Expression {
        get { return this.source.Expression; }
    }

    public IQueryProvider Provider {
        get { return new FakeRavenQueryProvider(this.source, this.QueryStatistics); }
    }

    public IRavenQueryable<T> Customize(Action<IDocumentQueryCustomization> action) {
        return this;
    }

    public IRavenQueryable<TResult> TransformWith<TTransformer, TResult>() where TTransformer : AbstractTransformerCreationTask, new() {
        throw new NotImplementedException();
    }

    public IRavenQueryable<T> AddQueryInput(string name, RavenJToken value) {
        throw new NotImplementedException();
    }

    public IRavenQueryable<T> Spatial(Expression<Func<T, object>> path, Func<SpatialCriteriaFactory, SpatialCriteria> clause) {
        throw new NotImplementedException();
    }

    public IRavenQueryable<T> Statistics(out RavenQueryStatistics stats) {
        stats = this.QueryStatistics;
        return this;
    }

    public IEnumerator<T> GetEnumerator() {
        return this.source.GetEnumerator();
    }

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

public class FakeRavenQueryProvider : IQueryProvider {
    private readonly IQueryable source;

    private readonly RavenQueryStatistics stats;

    public FakeRavenQueryProvider(IQueryable source, RavenQueryStatistics stats = null) {
        this.source = source;
        this.stats = stats;
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression) {
        return new FakeRavenQueryable<TElement>(this.source.Provider.CreateQuery<TElement>(expression), this.stats);
    }

    public IQueryable CreateQuery(Expression expression) {
        var type = typeof(FakeRavenQueryable<>).MakeGenericType(expression.Type);
        return (IQueryable)Activator.CreateInstance(type, this.source.Provider.CreateQuery(expression), this.stats);
    }

    public TResult Execute<TResult>(Expression expression) {
        return this.source.Provider.Execute<TResult>(expression);
    }

    public object Execute(Expression expression) {
        return this.source.Provider.Execute(expression);
    }
}
like image 60
Jon Adams Avatar answered Nov 15 '22 18:11

Jon Adams