NHibernate Unit Testing
When using NHibernate we generally want to test only three things, that properties are persisted, that cascade works as expected and that queries return the correct result. In order to do all of those, we generally have to talk to a real database, trying to fake any of those at this level is futile and going to be very complicated.
We can either use a standard RDBMS or use an in memory database such as SQLite in order to get very speedy tests.
I have a pretty big implementation of a base class for unit testing NHibernate in Rhino Commons, but that has so many features that I forget how to use it sometimes. Most of those features, by the way, are now null & void because we have NH Prof, and can easily see what is going on without resorting to the SQL Profiler.
At any rate, here is a very simple implementation of that base class, which gives us the ability to execute NHibernate tests in memory.
public class InMemoryDatabaseTest : IDisposable { private static Configuration Configuration; private static ISessionFactory SessionFactory; protected ISession session; public InMemoryDatabaseTest(Assembly assemblyContainingMapping) { if (Configuration == null) { Configuration = new Configuration() .SetProperty(Environment.ReleaseConnections,"on_close") .SetProperty(Environment.Dialect, typeof (SQLiteDialect).AssemblyQualifiedName) .SetProperty(Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName) .SetProperty(Environment.ConnectionString, "data source=:memory:") .SetProperty(Environment.ProxyFactoryFactoryClass, typeof (ProxyFactoryFactory).AssemblyQualifiedName) .AddAssembly(assemblyContainingMapping); SessionFactory = Configuration.BuildSessionFactory(); } session = SessionFactory.OpenSession(); new SchemaExport(Configuration).Execute(true, true, false, true, session.Connection, Console.Out); } public void Dispose() { session.Dispose(); } }
This just set up the in memory database, the mappings, and create a session which we can now use. Here is how we use this base class:
public class BlogTestFixture : InMemoryDatabaseTest { public BlogTestFixture() : base(typeof(Blog).Assembly) { } [Fact] public void CanSaveAndLoadBlog() { object id; using (var tx = session.BeginTransaction()) { id = session.Save(new Blog { AllowsComments = true, CreatedAt = new DateTime(2000,1,1), Subtitle = "Hello", Title = "World", }); tx.Commit(); } session.Clear(); using (var tx = session.BeginTransaction()) { var blog = session.Get<Blog>(id); Assert.Equal(new DateTime(2000, 1, 1), blog.CreatedAt); Assert.Equal("Hello", blog.Subtitle); Assert.Equal("World", blog.Title); Assert.True(blog.AllowsComments); tx.Commit(); } } }
Pretty simple, ah?
Comments
Brilliant, been looking for a succinct example like this for some time. Just what I needed!
Thanks for that, have passed this to a few people!
/me goes off to delete a bunch of code...
Yes, I used this scheme a lot, thanks to SqlLite, recreation of the db is really quick and this prevent test race, since for each test you will end with a clean db. Good technique.
alk.
Great, thank you!
I am having problems with our database schema tough. We are using MSSQL2005 and schemas for tables. However SQLite3 seems not to support this (correct me if I am wrong).
We're using FluentNhibernate for the configuration. Do you have any suggestions on how to tackle this, or a workaroud?
Sorry, discard the previous message. Found "SchemaIs" in fluentnhibernate and it's working.
What better timing!
I wrote something pretty much identical to this last week to test our event listeners, except with an SQL CE back end. And it's probably messier.
It has a couple of handy extensions though:
OpenSession() blows the SQL CE instance away and does a SchemaExport. This affects test performance somewhat so it might be set to SchemaExport per fixture.
Type[] MapTypes { get; }
Configuration AddConfiguration(Configuration configuration)
OpenSession is called per [Test]
MapTypes is called once on [SetUpFixture] to get a list of the relevant types (and maps in).
Configuration is exposed so that I can add interceptors on [SetUpFixture]
I shall blog it if I get some time.
Is there no documentation for Rhino Commons? Not even comments in the code - the most basic form of documentation?
If no, what's the point of writing the code - because just as you stated, you don't remember you have it or how to use it, so you just end up writing it over and over again.
Sounds like a waste of time to me - or perhaps I just misunderstood the post.
Do you have any tricks for inserting data when you must satisfy foreign key constraints? For example, lets say you wanted to write the same test for a BlogPost object which has a foreign key constraint on the Blog table.
Can you test BlogPost in isolation from Blog (e.g. not have to create a blog, get the id of the blog, and use that to create a blog post)?
Is this a mute issue because you would most likely test:
Create a Blog with x BlogPosts
Get the new Blog and validation BlogPosts.Count == x.
Just curious.
@What No Docs?
I dare say Rhino.Commons has simply evolved over the years in an Agile-"fashionever" style project.
And why bother code commenting nowadays when we've learnt to use SRP and OCD, naming conventions mean our classes are highly readable. Comments have just become pointless in most cases.
And the post wasnt a waste of time, it was clearly to demonstrate the simplest implementation of db testing in Nhibernate.
btw, thanks for that Ayende, just what I needed!
Chris,
There is no measurable perf for using schema export for SQLite with in memory database
Interesting. Will definitely consider changing that ASAP. I was not aware that there was an SQLite backend for NHibernate.
@Mark K
Yikes! Mark K stay away from me!
It must take a small army of programmers to clean up after you. I'd love to speak with the programmers who work at the companies you left. They are probably forced to throw your code away instead of reuse it because it takes less time to rewrite than understand the crap.
It's guys like you that give us all a bad name.
What No Docs?
Tomorrow there is a scheduled post that talks about Rhino Commons, wait for it
Patrick,
I create the object graph, save the root, and let persistence by readability do the rest.
FK constraint is also a constraint on the object model, after all.
Usually the builder will take care of that, though.
@ What no Docs (too afraid to use your real name)
What on earth are you talking about? Nobody saturates their code with comments anymore, except the usual class/method signatures.
Like I said, if you stick with SRP (google it if you dont know), and logical naming conventions, you're code will be more readable and wont need comments to explain every line.
Take a look in Rhino Commons, or Nhibernate, or Castle, or Fluent Nhibernate, fubuMVC: do all these project need cleaning up in your eyes because they lack comments for each line in a function?
Now please publicly critise Ayende for messy code and lack of code comments, Id love to be there to hear that conversation!
Very concise and useful post, I love it. After going back and forth on how to do my unit tests with NH this is pretty much what I settled on. Will probably incorporate the changes I see here.
Thanks
We've been doing this for a while on all our green field projects. I totally agree about perf of recreating the DB for each test case not being an issue. We have an older project that does tests against a real SQL Server instance and its much slower than the newer projects that test via sqlite.
I'm Bob K - satisfied Mark K? Dork.
Now off to Ayende since you seem to be too scared to call him out. Yeah the guy is good, very good, but he's no God. Commenting and documentation rules apply to him too.
I have read plenty of entries where Ayende was called in to write code on job sites where the programmers who would be performing maintenance on what he left behind were not up to his skill level.
If the guys left to perform maintenance behind him can't figure out what Ayende was doing due to lack of comments or documentation then shame on Ayende.
Now back to you, "Nobody saturates their code with comments anymore", yes they do. You're an idiot.
Bob,
That is interesting, since I cannot recall that. Since you read plenty of those, please post links to three of those. Shouldn't be a problem, right?
@Ayende
Are you seriously telling me you haven't written a post describing yourself on a clients site writing code where not everyone was a hot rod such as yourself, and that you had to write code that everyone on the team could understand, including junior programmers, because the client would be performing the maintenance.
Come on now, don't embarrass yourself and make me go find it. I hope you have indexed this blog with Lucene so I can go find it without having to troll through every entry on here.
Please tell me you don't only write complex code for the Gods. Please tell me you are humble enough to write for everyman too.
Bob,
You made the assertion, now back it up.
Offtopic:
@Ayende: Your post rate is so high, you actually plan the momentum to release specific posts? :) I mean: you say there's a scheduled post about Rhino Commons. What's the actual reasoning behind scheduling it, instead of just blurping it?
Grimace,
They go to the queue, I aim to a max of 2 posts per day.
There is no reasoning behind that, unless there is something special about the date.
@Ayende
My assertion was:
You have said, one requirement for the code you have written for some (ok, let's make this simple, only one client in your entire consulting career) required you to write the code in such a manner that junior programmers to hot rods could work on it once you left.
Now have you or have you not said that?
If no, then the implication is you're a God and I will bow and kiss your ring finger.
Until then, dont get annoyed and simply answer my original question with a question. Sheish get some balls.
@Ayende
Yawn, yawn. How many more do I need to go find.
From your blog on 12-24-06:
MS Consulting and The Client's Best Interest
posted on Sunday, December 24, 2006 8:39 PM
I can tell you that I (and We!) take a great deal of pride in what I create. I tend to not ship crappy code on a aesthetic basis. I had to go back to old projects, for code harvesting, additional development, bug fixes, etc. Producing crappy code means that I go home depressed, and I intend to do "this computers stuff" for a long time.
The core problem still exists, of course. One of the solutions is to have someone from the client side (either in-house or a third party) review the code and make sure that it matches the required standards. In most of my there is such a person, and it is my responabilities to walk them through the code and explain stuff (and have heated arguments ;-) ). The other is to have some sort of a support contract, which may include some clauses about fixing bugs in the application, acceptance tests, etc.
What I don't get is what is the point in testing against a schema created with Schema export, in an in memory database, won't that always succeed? Don't you want to test it against your actual schema? What does doing it this way actually prove?
@Andrew
I think that in memory testing is quicker than testing directly onto your db server.
The idea is that once your session is open to your in memory database, you call schemaexport to create all the tables. Then create several dummy domain objects and persist them. You can then unit test your repository logic (ie: does your session.createcriteria.... behave how it should?)
Andrew,
Well, plenty of us are using SchemaExport to generate the database.
It is much easier and cheaper to do so, after all.
And that ignore the issue that we are testing not only the schema, but a lot of other things as well. Consider cascades, that queries work, etc.
Bob,
Please read the post in context.
And please note what I am actually saying in that post.
Hand off is a particularly sensitive time in a project, emotions run hot, and someone that is tasked with responsibility for accepting ~150 KLOC tend to be very cautious.
I can tell you that I don't feel that I ever left a client with "it is me or no one else".
If I ever did, I would consider this a personal failure.
I think its because i'm using linq to nhibernate and dont have any createcritera with "magic strings" in them that it seems like it would always pass. I wasnt questioning the method, i'm a bit of an integration testing noob, trying to learn what i was missing or doing wrong! :) makes more sense now
@Ayende
Wow. I have no idea what you are talking about now. The thread between myself and Mark K was, Mark K said no one documents anything and I said that was crap, only losers dont document.
Mark K was feeling a little inadequate and suggested I bring you into the conversation because he needed your help, obviously thinking I wouldnt bring you in.
I was happy to bring you in because I have seen you state you document your code. For some reason you called me out and asked me to prove you document, so I found the post above.
Nuff said. You and I got no beef and Mark K has already run home to mom.
Ayende,
For very a long time, I'm bit confused about testing database situation. I thought your code not really a good one. By follows your code, you seems try to test NHibernate itself. It seems says "Hey NHibernate did you insert my data to DB correctly?". I trust NHibernate very much, so I think I must test another case, not just "insert correctly" way.
Do you have any suggestion in this situation? Hope you understand my point
Jimmy,
When testing NHibernate apps I usually test several things:
a) that I have correctly created the mapping
b) that I mapped all the persistent properties
c) that I have correctly defined cascades
And most importantly, that my queries return the correct results.
That is the most common thing that I really want to test
Ayende,
"And most importantly, that my queries return the correct results.
That is the most common thing that I really want to test "
I also thought this is the most important. We should put greater effort in this point.
Glad we have same concern. Thank you.
Hi,
i just had a question about the same topic on stackoverflow
(with most of the info being from old posts of Ayende anyway)
stackoverflow.com/.../using-sqlite-inmemory-db-...
has some useful info if you have hbm's like this :
class name="aTable" table="[dbo].[aTable]"
Comment preview