Unit Testing NHibernate Mappings
What is the best approach to map your domain classes to the database using NHibernate? After doing some tests, I believe that the best answer is: it doesn’t matter. As long as you have unit tests in place for it, you are covered.
The same mapping issue that you will face using HBM files, you will encounter using FHN or Mapping By Code. Because of that, I did some experiments on what would be a good approach to unit test NHibernate mappings.
Project Setup
All the source code used in this post is also available here. The NuGet packages used were the following.
- For the Domain library project: none.
- For the Persistence library project: NHibernate and System.Data.SQLite.Core.
- For the Tests library project: NHibernate, System.Data.SQLite.Core, NUnit and NUnit3TestAdapter.
The library NUnit3TestAdapter is all you need to run NUnit unit tests from any version of Visual Studio.
Using an In-Memory Helper Class
The following is the NHibernate helper class that we will be using to open new connections. It is designed to create an in-memory instance of SQLite database per unit test class or unit test method. Doing so, we can isolate our unit tests on its own contained database.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
using System;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Tool.hbm2ddl;
using System.Data.SQLite;
namespace Persistence
{
public class InMemorySessionHelper : ISessionHelper, IDisposable
{
private const string Connectionstring = "Data Source=:memory:";
private readonly Configuration _config;
private readonly ISessionFactory _sessionFactory;
private SQLiteConnection _connection;
public InMemorySessionHelper()
{
_config = new Configuration()
.DataBaseIntegration(db =>
{
db.Dialect<SQLiteDialect>();
db.Driver<SQLite20Driver>();
db.ConnectionString = Connectionstring;
db.LogFormattedSql = true;
db.LogSqlInConsole = true;
})
.AddAssembly("Persistence");
_sessionFactory = _config.BuildSessionFactory();
}
public void Dispose()
{
_connection?.Dispose();
}
public ISession OpenSession()
{
return _sessionFactory.OpenSession(GetConnection());
}
private SQLiteConnection GetConnection()
{
if (_connection == null)
{
_connection = new SQLiteConnection(Connectionstring);
_connection.Open();
SchemaExport se = new SchemaExport(_config);
se.Execute(true, true, false, _connection, null);
}
return _connection;
}
}
}
Base Test Fixture Class
The BaseFixture
class is designed to create a new instance of the database for each Test Fixture. It will help us create a new instance of InMemorySessionHelper
for each unit test. For what I did so far, running them per test class was enough.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using NHibernate;
using NUnit.Framework;
using Persistence;
namespace Tests.Mappings
{
public class BaseFixture
{
private InMemorySessionHelper _sessionHelper;
[OneTimeSetUp]
public void Setup()
{
_sessionHelper = new InMemorySessionHelper();
}
protected ISession OpenSession()
{
return _sessionHelper.OpenSession();
}
[OneTimeTearDown]
public void TearDown()
{
_sessionHelper.Dispose();
}
}
}
The OpenSession
method will be used to create new sessions inside each unit test. The in-memory database will stay alive until the connection is open. The TearDown
method will be responsible for disposing of the session helper, which will close the connection.
A Sample Unit Test
The sample test demonstrates how we can extend the BaseFixture
class to execute a unit test that validates the data mapping for a Template Fields property.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Domain.Entities;
using NUnit.Framework;
using System.Linq;
namespace Tests.Mappings
{
[TestFixture]
public class TemplateMappingFixture : BaseInMemoryFixture
{
[Test]
public void TemplateFieldsProperty()
{
object entityId = null;
using (var session = OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var template = new Template();
template.AddField(new Field("FieldOne"));
template.AddField(new Field("FieldTwo"));
entityId = session.Save(template);
transaction.Commit();
}
}
using (var session = OpenSession())
{
var entity = session.Get<Template>(entityId);
Assert.NotNull(entity);
Assert.NotNull(entity.Fields);
Assert.AreEqual(2, entity.Fields.Count);
Assert.Contains("FieldOne", entity.Fields.Select(x => x.Name).ToList());
Assert.Contains("FieldTwo", entity.Fields.Select(x => x.Name).ToList());
}
}
}
}
The point here is that you don’t need to hit a real database or even a development database, to validate that your ORM mapping is working as expected.