Ayende @ Rahien

It's a girl

Mocking NHibernate

My recent post caused quite a few comments, many of them about two major topics. The first is the mockability of my approach and the second is regarding the separation of layers.

This post is about the first concern. I don’t usually mock my database when using NHibernate. I use an in memory database and leave it at that. There are usually two common behaviors for loading data from the database. The first is when you need to load by primary key, usually using either Get or Load, the second is using a query.

If we are using Get or Load, this is extremely easy to mock, so I won’t touch that any further.

If we are using a query, I categorically don’t want to mock that. Querying is a business concern, and should be treated as such. Setting up an in memory database to be used with NHibernate is easy, you’ll have to wait until the 28th for the post about that to be published, but it is basically ten lines of code that you stick in a base class.

As such, I have no real motivation to try to abstract that away, I gain a lot, and lose nothing.

Comments

Vincent
04/18/2009 03:37 PM by
Vincent

Why the 28th?

Ayende Rahien
04/18/2009 07:24 PM by
Ayende Rahien

Vincent,

Because that is when it ended up in the queue.

Gokhan
04/18/2009 07:38 PM by
Gokhan

Will the in memory dialect support all your mappings?

Ayende Rahien
04/18/2009 07:41 PM by
Ayende Rahien

It supported everything that I tried so far.

Kelly Bourg
04/18/2009 08:13 PM by
Kelly Bourg

I'm guessing you are talking about using SqlLite in memory DB?

Simone
04/19/2009 02:24 PM by
Simone

At the pace Oren is posting lately, it will not be a long wait, A week at most :)

Ayende Rahien
04/19/2009 03:02 PM by
Ayende Rahien

Simone,

You don't follow. It is already posted, but it is posted for the 28th.

I am trying to put some distance between my posting, because an outpouring of 8 - 10 posts a day can be a bit.. challanging for the readers.

Right now I have posts all the way to beginning of May.

Simone
04/19/2009 03:06 PM by
Simone

Oh.. ok... I understood it was the 28th post in the queue :)

Yep, I agree... 10 posts a day is a bit "challenging", especially because your posts are really "intense".. not like the "announcement" kind of post of other blogs that have 10+ posts a day.

Hendry Luk
04/20/2009 04:34 AM by
Hendry Luk

So do you not have any "unit-test" for the query object itself at all? I.e. you always test the query in conjunction with the business services using it (possibly repeatedly).

I hate doing in-memory test, because setting up stub data, taking care of all its associations and database constraints, and evaluating the results, they are very tedious. Particularly if they have to be repeated everytime your service class uses the same query over and over again.

Which is why I only do such test in data-access layer for each individual query method.

Then in business service, I just mock them out (good old repository) and concentrate at business rule itself, and without repeating tedious data-driven unit-tests.

This also makes the business rule much easier to read at high level point of view, rather than being cluttered with long winded stub data (and DB related workarounds to take care of table associations and constraints) in the unit-tests of domain-layer or UI controllers.

I'm intrigued to see the unit-test of your services/controllers. In my imagination, it's something very bloated (with various concerns about stub data).

Out of curiousity, if querying is a business concern, which layer do you put them in the projects?

Hendry Luk
04/20/2009 05:00 AM by
Hendry Luk

Btw, IMHO, incorporating SQLite into domain-service test should be classified as integration test (as opposed to unit-test).

And when treated as a substitute for unit-tests, it seems to be an anti-pattern to me. Doesn't it bring too much baggage for the true purpose of unit-test and TDD?

Ayende Rahien
04/20/2009 08:15 AM by
Ayende Rahien

Hendry,

Queries exists in the business layer.

I don't have a real data layer, since I leave that to NH in almost all cases.

As for testing, you find that this is very easy to handle once you setup the system.

My controllers tend to be the place where I am only orchestrating things, real business logic is located elsewhere, so I don't really care that I test them using mini integration tests.

Hendry Luk
04/20/2009 08:47 AM by
Hendry Luk

Do you have any reference project that can slightly illusrate the idea?

I've looked at Pipe/Filter in MVC Storefront, and I have to say that the unit-tests for its Controllers are quite tediously data-driven, and starts getting repetitive. It's still fine in the small app like ecommerce storefronts, but I believe it could have gotten much worse in complex applications. I'm curious about your view about it, and a better way to it.

Btw I totally agree about OCP problem with repository that you bring up in the next post, the exact reason I've been looking for an alternative to repository pattern.

But so far, mockability is really my biggest stumble block.

Great series of write up btw, as always!

Ayende Rahien
04/20/2009 08:56 AM by
Ayende Rahien

Hendry,

I am sorry, nothing that is out in the open, no.

But take a look at the builder pattern, basically.

[Fact]

public void Willnotbuymoviefromuserifuserrented_moviedandreporteditlost()

{

Movie movie = new MovieBuilder("Lilies on the Vally");

User user = new UserBuilder("ayende");

var rental = user.Rent(movie);

user.ReportLost(rental);


AddToDatabase(user, movie, rental);


// run actual test


var controller = new BuyBackController();

var result = controller.BuyBack("Lilies on the Vally") as ViewResult;

Assert.Equal("CannotBuyBackRentedLostMovies", result.ViewName);

}

Hendry Luk
04/20/2009 09:29 AM by
Hendry Luk

Thanks Oren! The test does look very concise and clear.

After reading that, I looked at my code again, and realize I really should clean up all my builders.

The other major point I realize is that your test is written almost in acceptence test style, instead of striving for slicing the unit-test as thin/isolated as possible with a lot of nitty-gritty details (that make my tests bloated). Cleaner indeed.

That... I will steal ;)

Comments have been closed on this topic.