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?
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.
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.
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.
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.
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With