Posts Tagged Unit Testing

How-To: Using the N* Stack, part 4

This is part 4 of my series on ASP.NET MVC and NHibernate. If you’re not up to date, you can go check out:

  • Part 1 – Setting up the Visual Studio solution
  • Part 2 – Building the model
  • Part 3 – Mapping the model to the database

As promised, today, we’re going to test our mappings and get a little familiar with using NHibernate.

We’ll be using NUnit 2.5.2, but any recent version should work.

Disclaimer: I’m still learning some of this myself, so use at your own risk. This may not be considered best practice. Also, there’s almost certainly better ways to write these tests using one of the dozens of popular testing frameworks out there, but we’re using plain vanilla NUnit.

Let’s create a new Class Library project for our tests. We’ll call it NStackExample.Data.Tests. Now, in the data test project, add references to your core project, data project, NHibernate.dll, FluentNHibernate.dll, and NUnit.Framework.dll. If you’ve installed NUnit, NUnit.Framework.dll will be on the .NET tab. If you have multiple versions of NUnit installed, be sure to pick the right version.

SQL: Now in a convenient travel size

If you haven’t heard of SQLite before, you’re going to love this. It’s a tiny, self-contained, open-source SQL database engine in a DLL. Even better, it can run entirely in-memory and it’s blazing fast. Here’s how you get set up to use it:

  1. Download the SQLite ADO.NET Provider from here. Get the full version – the one named something like SQLLit-1.0.65.0-setup.exe. Install it, then grab a copy of System.Data.Sqlite.dll and put it in your Solution Items folder with all the other 3rd party libraries. If you’re running on a 64-bit operating system, grab the one from the bin\x64 directory. If not, use the one in bin.
  2. Download the SQLite library itself. Scroll down to Precompiled Binaries for Windows. It should be named something like sqlitedll-3_6_17.zip. Extract the SQLite3.dll to your Solution Items folder.
  3. In your data test project, add a reference to System.Data.SQLite.dll.
  4. Because SQLite3.dll was written in C and is completely unmanaged, we can’t add a direct reference to it. To ensure it gets put in the right place, we’re going to set it up as a content file. Right click on your data test project, choose Add Existing Item, then browse for SQLite3.dll. Add it. In Solution Explorer, it’ll be mixed in with the code for your project. Right click on it and choose properties. Set it to Copy Always. This will copy it to the bin\Debug or bin\Release folder every time your project is built, so it never gets forgotten.
  5. If you haven’t already, grab the code for the SQLiteDatabaseScope class from my previous post. Add it to your data test project.

A simple mapping test

Imports NUnit.Framework

<TestFixture()> _
Public Class CourseMappingTests

    <Test()> _
    Public Sub CanLoadAndSaveCourse()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid
                Dim Course As Course

                Using Tran = Session.BeginTransaction
                    ID = Session.Save(New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3})
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction
                    Course = Session.Get(Of Course)(ID)

                    Assert.AreEqual("SUBJ", Course.Subject)
                    Assert.AreEqual("1234", Course.CourseNumber)
                    Assert.AreEqual("Title", Course.Title)
                    Assert.AreEqual("Description", Course.Description)
                    Assert.AreEqual(3, Course.Hours)

                    Tran.Commit()

                End Using

            End Using
        End Using
    End Sub

End Class
using System;
using NUnit.Framework;
using NHibernate;

namespace NStackExample.Data.Tests
{
    [TestFixture]
    public class CourseMappingTests
    {

        [Test]
        public void CanSaveAndLoadCourse()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;
                    Course Course;

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid)Session.Save(new Course
                        {
                            Subject = "SUBJ",
                            CourseNumber = "1234",
                            Title = "Title",
                            Description = "Description",
                            Hours = 3
                        });
                        Tran.Commit();
                    }
                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Course = Session.Get<Course>(ID);

                        Assert.AreEqual("SUBJ", Course.Subject);
                        Assert.AreEqual("1234", Course.CourseNumber);
                        Assert.AreEqual("Title", Course.Title);
                        Assert.AreEqual("Description", Course.Description);
                        Assert.AreEqual(3, Course.Hours);

                        Tran.Commit();
                    }
                }
            }
        }
    }
}

Here’s how it works:

  • First, we get a fresh in-memory SQLite database with our schema built.
  • Put a new course in the database
  • Clear the session
  • Get the course back out of the database
  • Check to make sure each of our properties survived the trip. If they didn’t, fail the test.

There’s a few things that may be new to you.

  • Our class has the TestFixture attribute. This tells NUnit that this class contains tests.
  • Each subroutine has the Test attribute. This tells NUnit that this method is a test.
  • The SQLiteDatabaseScope is almost certainly new, considering I wrote it Friday. You can read my previous post for more information.

Use of implicit transaction is discouraged

You’re probably wondering why I would wrap such simple one-statement database logic in a transaction, especially a Session.Get, which is essentially a single select statement. Prior to writing this series, I wouldn’t have done it that way. Rookie mistake.

While doing research for this entry, I ran across an example test from Ayende. He was using transactions on everything,  even his calls to Session.Get. I asked him why and he sent me a link to this NHProfiler Alert. It’s important and not obvious – at least not to me – so with permission, I’ve quoted the entire page.

A common mistake when using a database is to use transactions only when orchestrating several write statements. In reality, every operation that the database is doing is done inside a transaction, including queries and writes (update, insert, delete).

When we don’t define our own transactions, it falls back into implicit transaction mode, where every statement to the database runs in its own transaction, resulting in a large performance cost (database time to build and tear down transactions), and reduced consistency.

Even if we are only reading data, we should use a transaction, because using transactions ensures that we get consistent results from the database. Hibernate assumes that all access to the database is done under a transaction, and strongly discourages any use of the session without a transaction.

Session session = sessionFactory.openSession();
try {
  Transaction tx = session.beginTransaction();
  try {
    //execute code that uses the session
  } finally {
    tx.commit();
  }
} finally {
  session.close();
}

Leaving aside the safety issue of working with transactions, the assumption that transactions are costly and that we need to optimize them is false. As previously mentioned, databases are always running in a transaction. Also, they have been heavily optimized to work with transactions.

The real question here is: Is the transaction per-statement or per-batch? There is a non-trivial amount of work that needs to be done to create and dispose of a transaction; having to do it per-statement is more costly than doing it per-batch.

It is possible to control the number and type of locks that a transaction takes by changing the transaction isolation level (and, indeed, a common optimization is to reduce the isolation level).

Hibernate treats the call to commit() as the time to flush all changed items from the unit of work to the database, and without an explicit call to Commit(), it has no way of knowing when it should do that. A call to Flush() is possible, but it is frowned upon because this is usually a sign of improper transaction usage.

I strongly suggest that you use code similar to that shown above (or use another approach to transactions, such as TransactionScope, or Castle’s Automatic Transaction Management) in order to handle transactions correctly.

Transaction and the second level cache

Another implication of not using explicit transactions with Hibernate is related to the use of the second level cache.

Hibernate goes to great length in order to ensure that the 2nd level cache maintains a consistent view of the database. This is accomplished by deferring all 2nd level cache updates to the transaction commit. In this way, we can assert that the data in the 2nd level cache is the one committed to the database.

Forgoing the use of explicit transactions has the effect of nulling the 2nd level cache. Here is an example that would make this clear:

try {
  Post post = session.get(Post.class, 1);
  // do something with post
} finally {
  session.close();
}

Even if the 2nd level cache is enabled for Post, it is still not going to be cached in the 2nd level cache. The reason is that until we commit a transaction, Hibernate will not update the cache with the values for the loaded entities.

This code, however, does make use of the 2nd level cache:

Session session = sessionFactory.openSession();
try {
  Transaction tx = sessionFactory.beginTransaction();
  try {
    Post post = session.get(Post.class, 1);
    // do something with post
  } finally {
    tx.commit();
  }
} finally {
  session.close();
}

 

A slightly more complicated mapping test

When an entity has a required parent, as in the case of our section, you must create and insert the parent before actually testing the child. We’re not testing the cascade here. That’s a separate test. In this case, section has two required parents: a course, and a term. Here’s the test:

    <Test()> _
    Public Sub CanLoadAndSaveCourse()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession

                Dim ID As Guid
                Dim Section As Section
                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                'We're not testing the cascade, so save the parents first...
                Using Tran = Session.BeginTransaction
                    Session.Save(Course)
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Using Tran = Session.BeginTransaction
                    ID = Session.Save(New Section With { _
                                      .Course = Course, _
                                      .FacultyName = "FacultyName", _
                                      .RoomNumber = "R1", _
                                      .SectionNumber = "1W", _
                                      .Term = Term})
                    Tran.Commit()
                End Using

                Session.Clear()

                Using Tran = Session.BeginTransaction
                    Section = Session.Get(Of Section)(ID)

                    Assert.AreEqual(Course, Section.Course)
                    Assert.AreEqual("FacultyName", Section.FacultyName)
                    Assert.AreEqual("R1", Section.RoomNumber)
                    Assert.AreEqual("1W", Section.SectionNumber)
                    Assert.AreEqual(Term, Section.Term)

                    Tran.Commit()

                End Using

            End Using
        End Using
    End Sub
        [Test]
        public void CanSaveAndLoadSection()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>) {
                using (ISession Session = Scope.OpenSession()) {

                    Guid ID;
                    Section Section;
                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3};
                    Term Term = new Term {
                        Name = "Fall 2009",
                        StartDate = new DateTime(2009,8,1),
                        EndDate = new DateTime(2009,12,1)};

                    // We're not testing the cascade here, so explicitly save these parent objects.
                    using (ITransaction Tran = Session.BeginTransaction()) {
                        Session.Save(Course);
                        Session.Save(Term);
                        Tran.Commit();
                    }

                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction()) {
                        ID = (Guid) Session.Save(new Section {
                                 Course = Course,
                                 FacultyName = "FacultyName",
                                 RoomNumber = "R1",
                                 SectionNumber = "W1",
                                 Term = Term});
                        Tran.Commit();
                    }

                    Session.Clear();

                    using (ITransaction Tran = Session.BeginTransaction()) {
                        Section = Session.Get<Section>(ID);

                        Assert.AreEqual(Course, Section.Course);
                        Assert.AreEqual("FacultyName", Section.FacultyName);
                        Assert.AreEqual("R1",Section.RoomNumber);
                        Assert.AreEqual("W1", Section.SectionNumber);
                        Assert.AreEqual(Term, Section.Term);

                        Tran.Commit();
                    }

                }
            }

        }

Testing the cascade

“Cascade what? “

In your application, when you’ve just registered a student for a whole bunch of classes, usually with several changes along the way, you don’t want to have to remember what entities were added, removed or changed. That’s just crazy. Thanks to the Cascade functionality in NHibernate, you don’t have to do that. Just save the student entity. If your mappings are correct, it just works™.

For some people, especially me, that’s a big if. That’s why we test our mappings.

    <Test()> _
    Public Sub CanCascadeSaveFromCourseToSections()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                'We're not testing the cascade of section -> term here
                Using Tran = Session.BeginTransaction
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

                Dim Section1 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "1", _
                        .Term = Term}

                Dim Section2 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "2", _
                        .Term = Term}

                Course.AddSection(Section1)
                Course.AddSection(Section2)

                'Test saving
                Using Tran = Session.BeginTransaction
                    ID = Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

                'Check the results
                Using Tran = Session.BeginTransaction
                    Course = Session.Get(Of Course)(ID)

                    Assert.AreEqual(2, Course.Sections.Count)
                    Assert.AreEqual(1, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section1)) _
                                    .Count(), "Course.Sections does not contain section 1.")

                    Assert.AreEqual(1, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section2)) _
                                    .Count(), "Course.Sections does not contain section 2.")

                    Tran.Commit()
                End Using
            End Using
        End Using
    End Sub
        [Test()]
        public void CanCascadeSaveFromCourseToSections()
        {
            using (SQLiteDatabaseScope Scope = new SQLiteDatabaseScope())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;

                    Term Term = new Term {
                                Name = "Fall 2009",
                                StartDate = new System.DateTime(2009, 9, 1),
                                EndDate = new System.DateTime(2009, 12, 1) };

                    //We're not testing the cascade of section -> term here
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Session.Save(Term);
                        Tran.Commit();
                    }
                    Session.Clear();

                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3 };

                    Section Section1 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "1",
                        Term = Term };

                    Section Section2 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "2",
                        Term = Term };

                    Course.AddSection(Section1);
                    Course.AddSection(Section2);

                    //Test saving
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid) Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Check the results
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Course = Session.Get(ID);

                        Assert.AreEqual(2, Course.Sections.Count);
                        Assert.AreEqual(1, Course.Sections
                                .Where(S => S.Equals(Section1)).Count(),
                                "Course.Sections does not contain section 1.");

                        Assert.AreEqual(1, Course.Sections
                                .Where(S => S.Equals(Section2)).Count(),
                                "Course.Sections does not contain section 2.");

                        Tran.Commit();
                    }
                }
            }
        }

The test above will make sure new and/or updated sections are saved when you save the course. Here’s how it works:

  • Get a fresh SQLite DB
  • Since we’re not testing terms, but we need one for our sections, build a term and stick it in the database.
  • Build a course and two sections.
  • Save the course
  • Clear the session
  • Get the course
  • Make sure it has our two sections

What should happen when you remove a section from a course? A parent course is required for each section. Remember, we specified not nullable in the mapping. More importantly, an orphaned section isn’t allowed in the real world. So, if a section is orphaned, it should be deleted. We need to write a test for that.

    <Test()> _
    Public Sub CanCascadeOrphanDeleteFromCourseToSections()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using Session = Scope.OpenSession
                Dim ID As Guid

                Dim Term As New Term With { _
                        .Name = "Fall 2009", _
                        .StartDate = New Date(2009, 9, 1), _
                        .EndDate = New Date(2009, 12, 1)}

                Using Tran = Session.BeginTransaction
                    'We're not testing the cascade of section -> term here
                    Session.Save(Term)
                    Tran.Commit()
                End Using
                Session.Clear()

                Dim Course As New Course With { _
                        .Subject = "SUBJ", _
                        .CourseNumber = "1234", _
                        .Title = "Title", _
                        .Description = "Description", _
                        .Hours = 3}

                Dim Section1 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "1", _
                        .Term = Term}

                Dim Section2 As New Section With { _
                        .FacultyName = "FacultyName", _
                        .RoomNumber = "R1", _
                        .SectionNumber = "2", _
                        .Term = Term}

                Course.AddSection(Section1)
                Course.AddSection(Section2)

                Using Tran = Session.BeginTransaction
                    Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

                'Test removing
                Course.RemoveSection(Section1)
                Using Tran = Session.BeginTransaction
                    ID = Session.Save(Course)
                    Tran.Commit()
                End Using
                Session.Clear()

                'Check the results
                Using Tran = Session.BeginTransaction
                    Course = Session.Get(Of Course)(ID)

                    Assert.AreEqual(1, Course.Sections.Count())

                    Assert.AreEqual(0, Course.Sections _
                                    .Where(Function(S As Section) _
                                               S.Equals(Section1)) _
                                    .Count(), "Course.Sections still contains section 1")

                    Tran.Commit()
                End Using

            End Using
        End Using
    End Sub
        [Test()]
        public void CanCascadeOrphanDeleteFromCourseToSections()
        {
            using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
            {
                using (ISession Session = Scope.OpenSession())
                {
                    Guid ID;

                    Term Term = new Term {
                        Name = "Fall 2009",
                        StartDate = new System.DateTime(2009, 9, 1),
                        EndDate = new System.DateTime(2009, 12, 1) };

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        //We're not testing the cascade of section -> term here
                        Session.Save(Term);
                        Tran.Commit();
                    }
                    Session.Clear();

                    Course Course = new Course {
                        Subject = "SUBJ",
                        CourseNumber = "1234",
                        Title = "Title",
                        Description = "Description",
                        Hours = 3 };

                    Section Section1 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "1",
                        Term = Term };

                    Section Section2 = new Section {
                        FacultyName = "FacultyName",
                        RoomNumber = "R1",
                        SectionNumber = "2",
                        Term = Term };

                    Course.AddSection(Section1);
                    Course.AddSection(Section2);

                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Test removing
                    Course.RemoveSection(Section1);
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        ID = (Guid) Session.Save(Course);
                        Tran.Commit();
                    }
                    Session.Clear();

                    //Check the results
                    using (ITransaction Tran = Session.BeginTransaction())
                    {
                        Course = Session.Get<Course>(ID);

                        Assert.AreEqual(1, Course.Sections.Count());

                        Assert.AreEqual(0, Course.Sections
                            .Where(S => S.Equals(Section1)).Count(),
                            "Course.Sections still contains section 1");

                        Tran.Commit();

                    }
                }
            }
        }

I hope you see where I’m going with this one. Except for query tests, which we’ll do when we write our DAOs, that’s it for NHibernate testing. We do the same types of tests for our other entity classes.

But…

So, I bet you’re thinking “This mess won’t compile and even if it did, almost all of your tests would fail!” Yep. If the tests always pass, why write them?

Normally, I’d at least declare those missing functions so the solution would compile, but in this case, the discussion of those issues fits better with our next topic: How do we fix the broken stuff?

Download links for the complete solution in both languages are coming soon.

Edit: Download VB.NET here or C# here. To simplify things, I’ve removed the StudentTerm entity from the model.

Jason

- Testy and in need of sleep.

References: Ayende’s blog post, Krzysztof Kozmic’s recent post on Devlio.us, Daniel Hoebling’s blog post, Ayende’s NHProfiler Alerts.

Tags:

NHibernate unit testing with SQLite in-memory DB

While doing research for my next post, I ran across a recurring problem. Everyone wanted to use an in-memory SQLite database for their NHibernate unit tests.

Evidently, there’s no way to get back to the in-memory database in it’s current state after all of the connections to that database have been closed. When you open another connection, you get a fresh blank database.

Here’s the solutions I found while searching around.

  • Ayende just uses a single NHibernate session, which usually only uses a single SQLite DB connection. In fact, he just uses one session per test fixture. I’m not that smart and I don’t know enough about the internals of NHibernate to be confident that I’m really testing what I think I’m testing. Using a fresh session and database for each test would make me a lot more confident.
  • Krzysztof Kozmic has resorted to using a SQLite database stored on disk. While it may be faster than blowing away and recreating a SQL Express database dozens of times, it’s still slower than using an in-memory database. Variations of this idea include storing the database on a MS-DOS 5.0-style RAM disk drive to recover some of the speed lost by using file I/O. Genius! Except, I’m not going to reconfigure my PC for a unit test. Sorry, it’s just not portable enough. That’s why the idea was rated down on Stack Overflow.
  • Someone (gotta love the anonymous bloggers!) suggested building a custom NHibernate DriverConnectionProvider and overriding the GetConnection and CloseConnection functions so that once opened, the connection would never be closed. This seems a bit extreme to me, and doesn’t provide a mechanism to get a fresh database when we need one.

Using the DriverConnectionProvider idea of maintaining a single connection, but with a controlled scope, I came up with this:

Imports FluentNHibernate.Cfg
Imports FluentNHibernate.Cfg.Db
Imports FluentNHibernate.Mapping
Imports NHibernate
Imports NHibernate.Tool.hbm2ddl
Imports System.Data.SQLite

Public Class SQLiteDatabaseScope(Of TClassFromMappingAssembly)
    Implements IDisposable

    Private Const CONNECTION_STRING As String = "Data Source=:memory:;Version=3;New=True;"

    Public Sub New()
        BuildConfiguration()
    End Sub

    Private m_Connection As SQLiteConnection
    Private m_SessionFactory As ISessionFactory

    Private Sub BuildConfiguration()
        m_SessionFactory = Fluently.Configure() _
                            .Database(GetDBConfig()) _
                            .Mappings(AddressOf GetMappings) _
                            .ExposeConfiguration(AddressOf BuildSchema) _
                            .BuildSessionFactory()
    End Sub

    Private Function GetDBConfig() As FluentNHibernate.Cfg.Db.IPersistenceConfigurer
        Return SQLiteConfiguration.Standard _
                    .ConnectionString(Function(cs As ConnectionStringBuilder) cs.Is(CONNECTION_STRING))
    End Function

    Private Sub GetMappings(ByVal x As MappingConfiguration)
        x.FluentMappings _
            .AddFromAssemblyOf(Of TClassFromMappingAssembly)() _
            .ExportTo(".")
    End Sub

    Private Sub BuildSchema(ByVal Cfg As NHibernate.Cfg.Configuration)
        Dim SE As New SchemaExport(Cfg)
        SE.Execute(False, True, False, GetConnection, Console.Out)
    End Sub

    Private Function GetConnection() As System.Data.SQLite.SQLiteConnection
        If m_Connection Is Nothing Then
            m_Connection = New SQLiteConnection(CONNECTION_STRING)
            m_Connection.Open()
        End If
        Return m_Connection
    End Function

    Public Function OpenSession() As ISession
        Return m_SessionFactory.OpenSession(GetConnection)
    End Function

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).
                If m_Connection IsNot Nothing Then m_Connection.Close()
                m_Connection = Nothing
            End If

            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

It’s the same idea as the DriverConnectionProvider, but with a different scope. We create and open a connection and maintain it for the lifespan of the SQLiteDatabaseScope object. So far, I haven’t found an instance where NHibernate closes the connection.

Each instance of SQLiteDatabaseScope uses a different in-memory database, thanks to the New=True property in the connection string. You should be able to run your tests in parallel. You can’t do that with any of the other solutions.

The generic type parameter should be set to one of your fluent mapping classes. It’s used to tell Fluent NHibernate which assembly to scan for fluent mappings.

It’s extremely easy to use.

Imports NUnit.Framework

<TestFixture> _
Public Class SomeNHibernateTestFixture

    <Test>
    Public Sub SomeNHibernateTest()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using SessionOne = Scope.OpenSession
                ' Do some stuff here...
            End Using

            Using SessionTwo = Scope.OpenSession
                ' Do some more stuff using the same in-memory SQLite DB
            End Using
        End Using
    End Sub

    <Test>
    Public Sub AnotherNHibernateTest()
        Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
            Using SessionThree = Scope.OpenSession
                ' Do some stuff here to a new, freshly-built database...
	    End Using
        End Using
    End Sub

End Class

Jason

- Concerned the problem is not really that simple.

 

Download the SQLiteDatabaseScope code in Visual Basic or C#. GoDaddy doesn’t like you to download .vb or .cs files, so don’t forget to take off the .txt extension before you add it to your project.

Tags: ,