12

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?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
mandreko
  • 1,766
  • 2
  • 12
  • 24
  • 2
    As a side idea, instead of mocking out RavenDB, have you thought about using the Embedded In Memory version? Mocking with Linq extension methods is (really) horrible to deal with, and I went with RavenDB because there is no real need to mock out the database. – Rangoric Apr 12 '12 at 20:24
  • I'm with @Rangoric - there's no need to mock out the `IDocumentSession` and `IDocumentStore` (or mocking any Ravendb) stuff when RavenDb has an `EmbeddedDocumentStore`. Dude, jump into http://jabbr.net/#/rooms/RavenDB and we'll all chat to you why/why not. – Pure.Krome Apr 13 '12 at 01:02
  • 2
    That is fine - except I am seeing RavenDB take too long to start up for my unit tests - saying that "It's designed not to be mocked" isn't ever the right answer. – Ronnie Nov 15 '12 at 09:25

2 Answers2

9

As mentioned in the comments, you should not be mocking RavenDB API in your tests.

RavenDB has excellent support for unit testing, thanks to InMemory mode:

[Test]
public void MyTest()
{
    using (var documentStore = new EmbeddableDocumentStore { RunInMemory = true })
    {
        documentStore.Initialize();

        using (var session = documentStore.OpenSession())
        {
            // test
        }
    }
}
Arnold Zokas
  • 8,306
  • 6
  • 50
  • 76
  • 7
    It takes almost 3 seconds on my machine to load and initialize the Raven embedded server. This is an unacceptable amount of time to add on to unit tests. – Mike Scott Apr 10 '13 at 17:01
  • @MikeScott 3 seconds doesn't sound right. What are the specs of your machine? – Arnold Zokas Apr 10 '13 at 21:10
  • How long does it take on yours? Have you stopwatched it in a unit test, including running the index creation tasks? Win7/64 on an Intel Core 2 Duo E4600 at 2.4GHz, 4GB RAM. – Mike Scott Apr 11 '13 at 09:20
  • 1
    The actual initialisation is sub-second. I have noticed, however, there is sometimes a high cost associated with initialising complex indexes.Your hardware looks like it should handle this just fine. – Arnold Zokas Apr 11 '13 at 09:35
  • Just to confirm: you are initialising it in-memory, correct? I'm sure you are - I just want to rule out the obvious. – Arnold Zokas Apr 11 '13 at 09:36
  • yes it's in memory. Initialization before index tasks takes the bulk of the time. I have only one index. So it's not index initialization. You say sub-second, but could we have an actual number? 900ms is a lot different from 50ms. – Mike Scott Apr 13 '13 at 15:25
  • If you create and initialize the document store in the TestInitialize method, then use that one instance of the document store in all tests, only the first test takes much time to initalize the document store. All subsequent tests run sub-millisecond (mine run in about 50ms.) – stricq Nov 17 '13 at 03:39
5

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);
    }
}
Jon Adams
  • 24,464
  • 18
  • 82
  • 120
  • Unit tests that are actually integration tests are a real problem. Unit tests should run within as short a time as possible to remain usable - and used - over a long time. – utunga Jan 27 '15 at 23:26
  • @utunga I'm not sure what you're trying to say. If you want to start a discussion about unit tests vs integration tests, that should probably be in chat or in a question at programmers.stackexchange.com. If that's not what you're talking about, and has something to do with this answer specifically, please clarify. – Jon Adams Jan 28 '15 at 15:40
  • 2
    sorry... what I actually meant to say is "this answer should be at the top because really unit tests should be designed to run within a short time..". Not to get into the whole unit tests vs integration test thing (and noting that Ayende seems to have switched his position towards integration tests and away from unit tests plus mocks or fakes) but way I look at it, while I'm OK with folks doing whatever they wish in this regard, the question was asking about Moq therefore that person is after a (true) unit test approach - so your answer should be preferred (even though its a fake not a mock). – utunga Jan 28 '15 at 22:02
  • worth noting also that Sam Ritchie's LINQ based FakeRavenQueryable is no longer up to date with latest IRavenQueryable interface in RavenDB.Client which is a bit of a shame (though probably harmless enough to just add a bunch of NotImplemented exceptions in most cases) – utunga Jan 28 '15 at 22:04
  • 2
    another note, the implementation of `IQueryProvider` doesn't work if you're using the async api. Raven extension methods such as `.ToListAsync()` complain about the query provider not being an `IRavenQueryProvider` – Simon Mar 03 '17 at 13:51