Mock setup for Marten.IDocumentSession (Moq/Nunit) - c#

I am trying to mock this statement:
IReadOnlyList<Student> students = await _session
.Query<Student>()
.Where(x => x.ClassId == classId)
.ToListAsync(cancellationToken);
My attempt at is:
private Mock<IDocumentSession> _sessionMock = new Mock<IDocumentSession>();
...
_sessionMock
.Setup(x => x
.Query<Students>()
.Where(y => y.ClassId == classId)
.ToListAsync(CancellationToken.None))
.ReturnsAsync(new List<Students));
But i am getting this error:
System.NotSupportedException : Unsupported expression: ... => ....ToListAsync(CancellationToken.None)
Extension methods (here: QueryableExtensions.ToListAsync) may not be used in setup / verification expressions.
I looked it up and read the answers I am getting from SOF and other places and understood that basically it's not easily possible to test extension methods.
The answers are old, like 5+ years, some from 2011, since then is there a way to get this to work?

TL;DR: I did not find any working solution to be able to mock IMartenQueryable
The IDocumentSession interface has the following inheritance chain:
IDocumentSession << IDocumentOperations << IQuerySession
Based on the source code the Query method is defined on IQuerySession interface like this
/// <summary>
/// Use Linq operators to query the documents
/// stored in Postgresql
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
IMartenQueryable<T> Query<T>();
The IMartenQueryable<T> is indeed an IQueryable<T>.
And that could be easily mocked via the MockQueryable.Moq.
List<Student> students = ...
var queryableMock = students.BuildMock();
_sessionMock.Setup(x => x.Query<Student>()).Returns(queryableMock);
I haven't tested the code, maybe you have to cast it to IMartenQueryable.
UPDATE #1
Based on this QueryableExtensions we should be able to convert IQueryable<Student> to IMartenQueryable<Student> via the As operator.
The As defined inside the JasperFx.Core.Reflection namespace.
I've created to convert
var queryableMock = students.AsQueryable().As<IMartenQueryable<Student>>();
//OR
var queryableMock = students.BuildMock().As<IMartenQueryable<Student>>();
but unfortunately it fails with InvalidCastException.
Tomorrow I'll continue my investigation from here.
UPDATE #2
As it turned out the As<T> function is just a simple wrapper around type cast... So, it did not help anything.
I've also tried to mock directly the IMartenQueryable<T> and it's ToListAsync member method. The problem with this approach is that you need to rewrite your production query to filter elements in memory << kills to whole point of having an IQueryable<T> (or a derived interface).
So, I gave up, I don't have any idea, how to do it properly. But as I have seen in the documentation and in the issues we are not the only ones :D

Related

MongoDb C# Driver Issue when trying to filter by enum value

I need some help, I'm new to MongoDb, and using the 2.4.4 MongoDb.Driver for .Net.
I've created a class to strongly type my collection and one of its fields is an enum, I've managed to make the query return a string instead of an int on that field when using find by adding a BsonRepresentation decorator.
My current issue is when trying to filter by that enum field, I'm trying to do the following:
return await _context.Contacts
.Find(x=> x.EnumField.ToString().Contains(searchTextParam)).ToListAsync();
So that I can filter by that field's text value, but that's throwing a runtime error:
System.ArgumentException: Unsupported filter: {document}{EnumField}.ToString().Contains("searchValue").
Thanks in advance,
Jorge
Generally speaking, the LINQ integration in the driver does not support any and every kind of LINQ statement, and in my experience, using .ToString() on a property is one of those scenarios that is not supported (i.e. cannot be parsed by the driver to be converted into a MongoDB query).
Taking inspiration from https://stackoverflow.com/a/34033905/159446, you might want to do something like this:
// assuming your class is also called Contacts
var filter = Builders<Contacts>.Filter.Regex(x => x.EnumField,
BsonRegularExpression.Create(searchTextParam));
return await _context.Contacts
.Find(filter).ToListAsync();
Augmenting the other answer, we can use FilterDefinitions with queryable using Inject() method. So just use regex filter on enum using builder and then inject it inside where linq method and we have queryable which will form a correct query.
In sample code below _.EnumType is the enum
var searchText = "SomeValue".ToLower();
var confidentialityFilter = Builders<MyType>.Filter.Regex(_ =>
_.EnumType, #$ "/{searchText}/is");
query = query.Where(_ =>
_.SomeTextField
.ToLower()
.Contains(searchText) || confidentialityFilter.Inject());
return query;

Strange exception in Linq-to-SQL

I developed the extension method:
public static IQueryable<T> Get<T>(this DataContext dataContext) where T : class
{
return dataContext.GetTable<T>();
}
Then I tried to use it:
dataContext.Get<XDbCurrency>().
Where(c => c.Id == XCurrencyId.USD).
SelectMany(
c =>
dataContext.Get<XDbCurrencyRate>().
Where(cr =>
cr.CurrencyId == c.Id &&
cr.StartOn <= DateTime.UtcNow &&
cr.EndOn > DateTime.UtcNow).
Take(1),
(c, cr) => cr).
ToArray();
I get the exception Member access 'XCurrencyId CurrencyId' of 'X.XDbCurrencyRate' not legal on type 'System.Data.Linq.Table``1[X.XDbCurrencyRate]. When I change Get back to GetTable - everything works! When I change the return type of Get from IQueryable<T> to Table<T> - it crashes.
UPDATE. I created XDataContextBase class, moved Get method there, inherited DBML-generated XDataContext from XDataContextBase - unfortunately it does not help. Magic!!
UPDATE. this.GetTable<XDbCurrencyRate> works, this.XDbCurrencyRates works, this.Get<XDbCurrencyRate> not works either Get is extension method or method in base class. By the way, internally this.XDbCurrencyRates is implemented absolutely like my Get. Magic!!
UPDATE. Looks like Linq-to-SQL supports only two things: either direct call of DataContext.GetTable<> or call of any-named property that returns System.Data.Linq.Table<XDbCurrencyRate>. If I create System.Data.Linq.Table<XDbCurrencyRate> Foo - it works, but when I create IQueryable<XDbCurrencyRate> Foo - it crashes with Queries with local collections are not supported.. Magic!!
Any ideas?
This is because the initial Get call is not part of the query tree. Just a normal method call. The second call is embedded in the tree and LINQ to SQL has no idea what that method means.
I know that EF would give up here immediately. I believe L2S is basically capable of running such a method locally and trying to inline the query that it returns. Here, that seems to now work out (or I'm wrong about that).
What I did was to create a custom LINQ provider that rewrites expression trees to execute such methods locally, inline their results into the query tree and then forward to LINQ to SQL. This is not trivial.
Maybe you can make AsExpandable from the so called "LINQ kit" (approximate spelling) work.

Unit Test SqlFunctions

I have a repository that exposes IQueryable and a service handles specific queries and here a few methods that make use of DbFunctions. In order to be testable, i create a fake repository with static list of elements and inject it into the service. Problem is, since my service queries a List and does not make use of database, i get the error "This function can only be invoked from LINQ to Entities.".
Is there any easier way for testing this than creating a fake DbFunctions and QueryProvider?
Thanks in advance
I'm tried to implement dateDiff function, and it's works for me
but we should think that in that case we test different functions
and we are testing not real behaviour
private class MySqlFunctions
{
[DbFunction("SqlServer", "DATEDIFF")]//EF will use this function
public int? DateDiff(string datePartArg, DateTime startDate, DateTime endDate)
{
var subtract = startDate.Subtract(endDate);
switch (datePartArg)
{
case "d":
return (int?)subtract.TotalDays;
case "s":
return (int?)subtract.TotalSeconds; // unit test will use this one
}
throw new NotSupportedException("Method supports only s or d param");
}
}
Then in linq code
var sqlFunctions = new MySqlFunctions();
var result = matches.Average(s => sqlFunctions.DateDiff("s", s.MatchCreated, s.WaitingStarted);
You can't reliably fake SQL due to the fact that LINQ to objects behaves differently in many cases than LINQ to SQL. For example, where (new [] { "asdf"}).Contains("ASDF") returns false in LINQ to objects, the same type of query in LINQ to SQL would return true. The best thing I've found to do is to separate the retrieval of data from the action on that data. Maybe create some kind of PersonManager that takes an IPersonRepository as a dependency. You can fake/mock IPersonRepository and use that to test that PersonManager does what it's supposed to do under various circumstances.
since i hit the same problem recently, and opted for a simpler solution, wanted to post it here.. this solution requires no Shims, Mocking, nothing expansive etc.
Pass a 'useDbFunctions' boolean flag to your method with default value as true.
When your live code executes, your query will use DbFunctions and everything will work. Due to the default value, callers need not worry about it.
When your unit tests invoke the method to test, they can pass useDbFunctions: false.
In your method, you can make use the flag to compose your IQueryable..
if useDbFunctions is true, use the DbFunctions to add the predicate to the queryable.
if useDbFunctions is false, then skip the DbFunctions method call, and do an explicit C# equivalent solution.
This way, your unit tests will check almost 95% of your method in parity with live code. You still have the delta of "DbFunctions" vs. your equivalent code, but be diligent about it and the 95% will look like a lot of gain.
public SomeMethodWithDbFunctions(bool useDbFunctions = true)
{
var queryable = db.Employees.Where(e=>e.Id==1); // without the DbFunctions
if (useDbFunctions) // use the DbFunctions
{
queryable = queryable.Where(e=>
DbFunctions.AddSeconds(e.LoginTime, 3600) <= DateTime.Now);
}
else
{
// do db-functions equivalent here using C# logic
// this is what the unit test path will invoke
queryable = queryable.Where(e=>e.LoginTime.AddSeconds(3600) < DateTime.Now);
}
var query = queryable.Select(); // do projections, sorting etc.
}
Unit tests will invoke the method as:
SomeMethodWithDbFunctions(useDbFunctions: false);
Because unit tests would have setup local DbContext entities, the C# logic/DateTime functions would work.

Often used LINQ returned from method

I have 1 LINQ which used so much. I try create the method which return this LINQ like:
public static System.Linq.Expressions.Expression<Func<MyEntity, bool>> GetFilteredEntity() {
return x => true/*Some condition*/;
}
public static Func<MyEntity, bool> GetFilteredEntity() {
return x => true/*Some condition*/;
}
And use this like
db.MyEntities.Where(GetFilteredEntity());
is successfull, But! I need use it like
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity()));
This code compiled too, but every time when i use it, i got the Error:
System.InvalidOperationException: There is already an open DataReader associated with this Command which must be closed first.
,even:
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity())).ToList();
throw this Exception too.
But,
db.ParentEntities.Where(entity => entity.MyEntities.Where(x => true/*Some condition*/))
still works fine!
So why it happend, and have some way to get round this?
Final working code
public static Expression<Func<MyEntity, bool>> GetFilteredEntity() {
return x => true/*Some condition*/;
}
and
var expression = GetFilteredEntity();
db.ParentEntities.Where(entity => entity.MyEntities.AsQueryable().Where(expression ));
Also .AsQueryable() thanks to Passing func as parameter in Linq to Entities and 'Internal .NET Framework Data Provider error 1025' error
In your first example the function is called and translated into an expression before it is even sent to the query provider. In the next two examples the function call is embedded within an expression that is sent to the query provider, and that query provider doesn't know what to do with that function call, so it just throws an exception. When you embed the actual expression in another expression, there is no function call to confuse the query provider.
As for a solution, just pull out the function call into a variable. The query provider is smart enough to see that you used a closed over variable, and will pull out its value. For a function call it's just not sure if it should evaluate it or try to translate it into something that should be done on the DB's end. Trying to do some of both would just be very confusing and hard to work with, both for the query provider, and for people using it. To simplify matters, function calls with expressions are never executed prior to sending the query. As for a closed over variable, there is no other way that it could be treated, so there isn't any other behavior to confuse it with.
var expression = GetFilteredEntity();
db.ParentEntities.Where(entity => entity.MyEntities.Where(expression ));
Looks like LazyLoading might be the culprit, have you tried popping the ToList() on the parameters?
db.ParentEntities.Where(entity => entity.MyEntities.Where(GetFilteredEntity()).ToList());

Error on Linq To Entities method contains NotSupportedException

I've checked other questions and I believe this should work:
private static List<Duck> GetDucks(this DataServiceContext ctx, params int[] ids)
{
return ctx.CreateQuery<Duck>("Ducks").Where(x => ids.Contains(x.DuckID)).ToList();
}
I got a NotSupportedException: The method 'Contains' is not supported.
I changed the query to
Where(x => ids.Any(id => id == x.DuckID)
I received the message: The method 'Any' is not supported when MaxProtocolVersion is less than '3.0'
I then constructed the DataServiceContext a different way:
public static DataServiceContext Context
{
get { return new DataServiceContext(baseUri, DataServiceProtocolVersion.V3); }
}
I then received the message: NotSupportedException: The source parameter for the 'Any' method has to be either a navigation or a collection property
I think I could do with some advice before going any further.
EDIT
I did use MaxProtocolVersion 3 both client and server side.
In any case, this particular error message has gone away for now as I am no longer trying to return a subset of data. I simply get the full list of entities up front to work with (though this will need further consideration/optimization). I mistakenly believed that if I had a temp DataServiceContext that was empty I would have to retrieve the entities I wanted to update and then save them back (influenced by various examples on performing updates I found). Of course the method AttachTo(EntitySetName, entity) is the correct solution. Then you can call UpdateObject(entity) prior to TempContext.SaveChanges().
From what I have learned it looks like LINQ support in this scenario is limited, though I still do not know why contains does not work on a DataServiceQuery. The MSDN documentation suggests it is supported. Queries to the DataServiceContext resolve urls in OData format:
http://services.odata.org/OData/OData.svc/Category(1)/Products?$top=2&$orderby=name
The solution I used - adding a WCF Data Service operation/method:
[WebGet]
public IQueryable<Thing> GetThingsByID(string ids)
{
return CurrentDataSource.Things.Where(x => ids.Contains(x.ThingID.ToString())).AsQueryable();
}
The above should allow me to filter:
WCF Data Service operations
Calling Service Operations
I pass in ids as CSVs as only primitive data types may be passed as parameters
Note:
Contains works on the DBContext.

Categories

Resources