ENPT

Testes de unidade para mapeamentos do NHibernate

Qual é a melhor abordagem para mapear suas classes de domínio para o banco de dados usando NHibernate? Depois de fazer alguns testes, acredito que a melhor resposta é: não importa. Desde que você tenha testes de unidade para isso, você está coberto.

O mesmo problema de mapeamento que você enfrentará usando arquivos HBM, você encontrará usando FHN ou Mapping By Code. Por causa disso, fiz alguns experimentos sobre qual seria uma boa abordagem para testar mapeamentos do NHibernate com testes de unidade.

Configuração do projeto

Todo o código-fonte usado neste post também está disponível aqui. Os pacotes NuGet usados foram os seguintes.

  • Para o projeto da biblioteca de Domain: nenhum.
  • Para o projeto da biblioteca de Persistence: NHibernate e System.Data.SQLite.Core.
  • Para o projeto da biblioteca de Tests: NHibernate, System.Data.SQLite.Core, NUnit e NUnit3TestAdapter.

A biblioteca NUnit3TestAdapter é tudo o que você precisa para executar testes de unidade do NUnit em qualquer versão do Visual Studio.

Usando uma classe auxiliar em memória

A seguir está a classe auxiliar do NHibernate que usaremos para abrir novas conexões. Ela foi projetada para criar uma instância em memória do banco de dados SQLite por classe de teste ou método de teste. Assim, podemos isolar nossos testes de unidade em seu próprio banco de dados contido.

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;
        }
    }
}

Classe base de test fixture

A classe BaseFixture foi projetada para criar uma nova instância do banco de dados para cada Test Fixture. Ela nos ajudará a criar uma nova instância de InMemorySessionHelper para cada teste de unidade. Pelo que fiz até agora, executá-los por classe de teste foi suficiente.

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();
        }
    }
}

O método OpenSession será usado para criar novas sessões dentro de cada teste de unidade. O banco de dados em memória permanecerá ativo enquanto a conexão estiver aberta. O método TearDown será responsável por descartar o session helper, o que fechará a conexão.

Um exemplo de teste de unidade

O teste de exemplo demonstra como podemos estender a classe BaseFixture para executar um teste de unidade que valida o mapeamento de dados de uma propriedade de campos de Template.

using Domain.Entities;
using NUnit.Framework;
using System.Linq;

namespace Tests.Mappings
{
  [TestFixture]
  public class TemplateMappingFixture : BaseFixture
  {
    [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());
      }
    }
  }
}

O ponto aqui é que você não precisa acessar um banco de dados real, nem mesmo um de desenvolvimento, para validar que o mapeamento do seu ORM está funcionando como esperado.