Tuesday, August 9, 2011

How to make your controller unit-testing friendly

There are some code that don't have anything to do with program's correctness, and could fail your unit test, and you need to conditionally suppress them. Case in point, you cannot apply eager loading on mocked IQueryable with NHibernate's FetchMany, you'll will encounter this error when you do so.

System.InvalidOperationException: There is no method 'FetchMany' on type 'NHibernate.Linq.EagerFetchingExtensionMethods' that matches the specified arguments

Don't do this:
public MovieController : Controller
{
    
    IRepository<Movie> _db;
    public MovieController(IRepository<Movie> db)
    {
        _db = db;
    }

    public ViewResult ShowMovieInfo(int id)
    {
        var query = 
                (from x in _db.All
                where x.MovieId == id 
                select x).FetchMany(x => x.Quotes);

        var movie = query.Single();
        return View(movie);
    }
}

When you perform unit testing on your controller, it will fail on FetchMany; NHibernate will throw an exception when the IQueryable(the All property of _db repository) didn't came from NHibernate, e.g. mocked IQueryable.

To make your controller unit testing-friendly, isolate and conditionally suppress anything that are not a factor on program correctness. NHibernate's FetchMany is one of those, even without FetchMany you can verify the program correctness. FetchMany is for performance optimization only.


Do this instead:

public MovieController : Controller
{
    
    IRepository<Movie> _db;
    public MovieController(IRepository<Movie> db)
    {
        _db = db;
    }

    public ViewResult ShowMovieInfo(int id)
    {
        var query = 
                from x in _db.All
                where x.MovieId == id 
                select x;

        if (query.Provider.GetType() == typeof(NHibernate.Linq.NhQueryProvider))
            query = query.FetchMany(x => x.Quotes);

        var movie = query.Single();
        return View(movie);
    }
}


Note: On NHibernate 3.2, there's no more NhQueryProvider, it's replaced with DefaultQueryProvider

4 comments:

  1. Thanks, glad I found this!

    ReplyDelete
  2. I took the long way around instead; built my own set of these Fetch extension methods, and a wrapper for the INhFetchRequest, that wrapped NHibernate's own. It had the added advantage, when I first built it, of preventing consuming code from having to directly reference NH instead of my encapsulated Repository interface/classes. "No problem you can't solve with another layer of abstraction, except for having too many".

    ReplyDelete
  3. Information here is valid. Great post.

    ReplyDelete