In my project I am using the following approach to querying data from the database:
IRepository.Get<T>
instead of IRepository<T>.Get
. NHibernates ISession
is an example of such a repository.Use extension methods on IQueryable<T>
with a specific T
to encapsulate recurring queries, e.g.
public static IQueryable<Invoice> ByInvoiceType(this IQueryable<Invoice> q,
InvoiceType invoiceType)
{
return q.Where(x => x.InvoiceType == invoiceType);
}
Usage would be like this:
var result = session.Query<Invoice>().ByInvoiceType(InvoiceType.NormalInvoice);
Now assume I have a public method I want to test that uses this query. I want to test the three possible cases:
My problem now is: What to mock?
ByInvoiceType
because it is an extension method, or can I?Query
for the same reason.Convert the DbSet properties on the DbContext derived class into IDbSet properties. Add a section that generates an interface for the DbContext derived class containing the IDbSet properties and any other methods (such as SaveChanges) that you'll need to mock. Implement the new interface in the DbContext derived class.
Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type.
What is extension method? Extension methods in C# are methods applied to some existing class and they look like regular instance methods. This way we can "extend" existing classes we cannot change. Perhaps the best example of extension methods are HtmlHelper extensions used in ASP.NET MVC.
After some more research and based on the answers here and on these links, I decided to completely re-design my API.
The basic concept is to completely disallow custom queries in the business code. This solves two problems:
IQueryable<T>
and which are not.In the business code, a query now looks like this:
IEnumerable<Invoice> inv = repository.Query
.Invoices.ThatAre
.Started()
.Unfinished()
.And.WithoutError();
// or
IEnumerable<Invoice> inv = repository.Query.Invoices.ThatAre.Started();
// or
Invoice inv = repository.Query.Invoices.ByInvoiceNumber(invoiceNumber);
In practice this is implemented like this:
As Vytautas Mackonis suggested in his answer, I am no longer depending directly on NHibernate's ISession
, instead I am now depending on an IRepository
.
This interface has a property named Query
of type IQueries
. For each entity the business layer needs to query there is a property in IQueries
. Each property has its own interface that defines the queries for the entity. Each query interface implements the generic IQuery<T>
interface which in turn implementes IEnumerable<T>
, leading to the very clean DSL like syntax seen above.
Some code:
public interface IRepository
{
IQueries Queries { get; }
}
public interface IQueries
{
IInvoiceQuery Invoices { get; }
IUserQuery Users { get; }
}
public interface IQuery<T> : IEnumerable<T>
{
T Single();
T SingleOrDefault();
T First();
T FirstOrDefault();
}
public interface IInvoiceQuery : IQuery<Invoice>
{
IInvoiceQuery Started();
IInvoiceQuery Unfinished();
IInvoiceQuery WithoutError();
Invoice ByInvoiceNumber(string invoiceNumber);
}
This fluent querying syntax allows the business layer to combine the supplied queries to take full advantage of the underlying ORM's capabilities to let the database filter as much as possible.
The implementation for NHibernate would look something like this:
public class NHibernateInvoiceQuery : IInvoiceQuery
{
IQueryable<Invoice> _query;
public NHibernateInvoiceQuery(ISession session)
{
_query = session.Query<Invoice>();
}
public IInvoiceQuery Started()
{
_query = _query.Where(x => x.IsStarted);
return this;
}
public IInvoiceQuery WithoutError()
{
_query = _query.Where(x => !x.HasError);
return this;
}
public Invoice ByInvoiceNumber(string invoiceNumber)
{
return _query.SingleOrDefault(x => x.InvoiceNumber == invoiceNumber);
}
public IEnumerator<Invoice> GetEnumerator()
{
return _query.GetEnumerator();
}
// ...
}
In my real implementation I extracted most of the infrastructure code into a base class, so that it becomes very easy to create a new query object for a new entity. Adding a new query to an existing entity is also very simple.
The nice thing about this is that the business layer is completely free of querying logic and thus the data store can be switched easily. Or one could implement one of the queries using the criteria API or get the data from another data source. The business layer would be oblivious to these details.
ISession would be the thing you should mock in this case. But the real problem is that you should not have it as a direct dependency. It kills testability the same way as having SqlConnection in the class - you would then have to "mock" the database itself.
Wrap ISession with some interface and it all becomes easy:
public interface IDataStore
{
IQueryable<T> Query<T>();
}
public class NHibernateDataStore : IDataStore
{
private readonly ISession _session;
public NHibernateDataStore(ISession session)
{
_session = session;
}
public IQueryable<T> Query<T>()
{
return _session.Query<T>();
}
}
Then you could mock IDataStore by returning a simple list.
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