Friday, June 21, 2013

Mocking NHibernate with Typemock Isolator is Awesome

June 21, 2013 Posted by Jason Irwin , No comments

Note: I am not affiliated in any way with Typemock

Unit testing methods that contain NHibernate calls is notoriously difficult. In fact, a common suggestion is to give up and use an in memory database instead. This is fine (I've personally tried it on multiple occasions), and it works I suppose, but has always felt more like integration testing than unit testing (what happened to "no external dependencies"?..perhaps I'm a purist) and undoubtedly slows down the test suite - especially in all but non trivial cases where reference data needs to be loaded.

I'm a novice Isolator user and messed around with the RecordExpectations syntax for a little bit before realizing just how simple Isolator makes things. Once you understand the warnings that Typemock puts out there, things get really easy. Really easy.

First Attempt
Let's say I wanted to fake the count of the products currently mapped to a specific category on my website. Normally the chained nature of NHibernate queryover calls requires mocking a LOT of stuff.

When you attempt to use Isolator's Isolate.WhenCalled method like in the case below, you may get an error message. Don't get despondent!

Code

 var session = Isolate.GetFake<ISession>(controller);

 Isolate.WhenCalled(()=>session.QueryOver<CategoryProduct>()
        .Where(x => x.Category == category && x.IsEnabled == true)
        .RowCount())
        .WillReturn(1);

Error Message
There is nothing better (or more rare) than getting a useful error message. In fairness, Typemock provides just that - not only highlighting the issue, but suggesting a fix too. Wonderful!

Test 'mytest' failed:
    TypeMock.ArrangeActAssert.NestedCallException : 
*** WhenCalled does not support using a method call as an argument.
-   To fix this pass null instead of Expression.Lambda()

***
* Example - this would work:
-   MyObj argument = Something.Other().GetStuff();
-   Isolate.WhenCalled(() => ObjUnderTest.MethodUnderTest(argument))...;
***
* Example - this would not work:
-   Isolate.WhenCalled(() => ObjUnderTest.MethodUnderTest(Something.Other().GetStuff()))...;
    at cx.a()
    at g4.a()
    at bh.a(Boolean A_0)
    at dl.b(Boolean A_0)
    at ij.b(Boolean A_0)
    at ij.a(Object A_0, Boolean A_1, Func`1 A_2, Action A_3, Action A_4, Action A_5, Boolean A_6)
    at ij.e(Object A_0)
    at TypeMock.ArrangeActAssert.ExpectationEngine`1.a(TResult A_0)

Updated Syntax
The issue here is that in the chained Where call I am using a lambda expression to filter my query. Since Typemock doesn't support methods being sent as arguments (and my lambda here is nothing more than an inline anonymous function) I need to break the lambda out into a standalone expression (Resharper is your friend).

What I end up with is the following terse (certainly compared to the alternative) syntax to mock a chained NHibernate call.

Expression<Func<CategoryProduct, bool>> expression = x => x => x.Category == category && x.IsEnabled == true;

Isolate.WhenCalled(()=>session.QueryOver<CategoryProduct>().Where(expression).RowCount()).WillReturn(1);

While Isolator is pretty pricey, it's this kind of power that separates it from the pack. I defy anyone to show me a better/easier way to do this.

0 comments: