Technology with opinion

Tuesday, June 09, 2009

Unit Testing NHIbernate with SessionScopes

Ayende has a great article on Unit Testing NHibernate. However if are you using NHibernate Session Scopes you need to adapt it. I wanted to keep my tests simple, I want to test my DAOs and inject into them an ISessionFactory since this is where it's getting it's Session from. SessionScopes are a nice way in NHibernate not have to worry about managing your sessions, you want to control the behavior. You may end up doing this with AOP or in your web request however that's not pertinent to my test since the design of my DAOs have the Session Factory Injected.

We are reusing DAOs within Web code to integration code and the session management behavior is different in each one. I am also using the Spring.Net however this should work anywhere that you are injecting ISessionFactory and you are using SessionFactory.GetCurrentSession() to get your session.

Basically we're using the same code as in his example except we are using Rhino Mock to actually mock the SessionFactory. In addition we are exposing the SessionFactory so that you can inject it into your DAOs. We are still exposing the Session for use in your test fixture.

    public class InMemoryDatabaseTest : IDisposable
{
private static Configuration Configuration;
private MockRepository _mocks;
private static ISessionFactory _realSessionFactory;
private ISession _session;
private ISessionFactory _sessionFactory;

protected ISession Session
{
get { return _sessionFactory.GetCurrentSession(); } // simulates how DAOs are getting their Sessions
}

///
/// Gets the SessionFactory
///
public ISessionFactory SessionFactory
{
get { return _sessionFactory; }
}

public InMemoryDatabaseTest(Assembly assemblyContainingMapping)
{
_mocks = new MockRepository();

if (Configuration == null)
{
Configuration = new Configuration()
.SetProperty(NHibernate.Cfg.Environment.ReleaseConnections, "on_close")
.SetProperty(NHibernate.Cfg.Environment.Dialect, typeof(SQLiteDialect).AssemblyQualifiedName)
.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, typeof(SQLite20Driver).AssemblyQualifiedName)
.SetProperty(NHibernate.Cfg.Environment.ConnectionString, "data source=:memory:")
.SetProperty(NHibernate.Cfg.Environment.ProxyFactoryFactoryClass, typeof(DefaultProxyFactoryFactory).AssemblyQualifiedName)
.AddAssembly(assemblyContainingMapping);
_realSessionFactory = Configuration.BuildSessionFactory();
}

// Here we are mocking the Session Factory because we are using Session Scopes.
// However we want to return the same session every time
_sessionFactory = _mocks.CreateMock<ISessionFactory>();
_session = _realSessionFactory.OpenSession();
Expect.Call(_sessionFactory.GetCurrentSession()).IgnoreArguments().Return(_session).Repeat.Any();
_mocks.ReplayAll();

new SchemaExport(Configuration).Execute(true, true, false, true, _session.Connection, Console.Out);
}

public void Dispose()
{
Session.Dispose();
}
}
The Mock is the glue that makes this work so that you can adhere to the restrictions of your DAO implementation. Then you can execute the test much like Ayende does in his own, regardless of how your perform transaction management in your actual application.

    [TestFixture]
public class FelineDao_Tests : InMemoryDatabaseTest
{
public FelineDao_Tests() : base(typeof(Feline).Assembly) { }

[TestFixtureSetUp]
public void Initialize()
{
// Insert Test data
using (ITransaction tx = Session.BeginTransaction())
{
Session.Save(GetSnowLeopard());
Session.Save(GetDomesticCat());
tx.Commit();
}

Session.Clear();
}

[Test]
public void FindAll_Retrieval()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
IList list = dao.FindAll();
Assert.AreEqual(2, list.Count);
tx.Commit();
}
}

[Test]
public void FindById_Retrieval_InValid_Id()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
Feline o = dao.FindById(3);
Assert.IsNull(o);
tx.Commit();
}
}

[Test]
public void FindById_Retrieval_Valid_Id()
{
using (ITransaction tx = Session.BeginTransaction())
{
Dao dao = new Dao();
dao.SessionFactory = SessionFactory;
Feline o = dao.FindById(1);
Assert.IsNotNull(o);
tx.Commit();
}
}

private Feline GetSnowLeopard()
{
Feline feline = new Feline();
feline.Name = "Snow Leopard";
feline.Length = 74;
return feline;
}

private Feline GetDomesticCat()
{
Feline feline = new Feline();
feline.Name = "Domestic Cat";
feline.Length = 24;
return feline;
}
}

Props to Ayende for the original implementation of this and for his work on Rhino Mocks.

2 comments:

Berryl Hesh said...

What is the session context you are using, and where do you set and bind it to the session factory?

If the context is ThreadStaticSessionContext wouldn't GetCurrentSession() always be returning the same session anyway?

Cheers,
Berryl

Scott White said...

it would return the same session, basically I'm faking contextual sessions by mocking the SessionFactory. I have a new version I'll post that actually binds it to a thread which works similarly.