xUnitでのユニットテスト実行時に、NHibernate + SQLiteなインメモリDBをテストに利用する


# 概要

[xUnit](https://xunit.github.io/)でユニットテストをする際、テスト実行のためにデータベースが必要となるテストケースをどのように書いたら良いかまとめました。

ここでは[NHibernate](http://nhibernate.info/) + [SQLite](https://www.nuget.org/packages/System.Data.SQLite)の組み合わせの場合のコードを例示していますが、[Entity Framework](https://github.com/aspnet/EntityFramework) + SQL Databaseなどの場合も同じような考え方で実装できます。


# 実装方針

テストケースを記述したクラスで`IClassFixture<TFixture>`を継承しておくと、xUnitによってテストが実行される前に`TFixture`クラスがインスタンス化され、そのインスタンスがテストケースのクラスのコンストラクタに渡されます。

従って、今回のようにデータベースを必要とするテストでは、次のような方針でテストコードを実装します。

1. `TFixture`クラスにDB周りの初期化コードを書く
2. テストケースのクラスでは、`IClassFixture<TFixture>`を継承して、コンストラクタでTFixtureインスタンスを受け取る
3. テストコード内でTFixtureインスタンスからDBのセッションを取り出してDB操作する


# 実際のコード例

## SQLiteのセッションを生成するためのFixtureクラス例

Fixtureクラス側では、インメモリなSQLiteに接続するようなコードを、NHibernateとFluentNHibernateを使って実装しました。

なお、IDisposableを実装しておくと、xUnitがテスト終了時にDispose()を呼び出してくれるため、DBの接続を閉じるコードなどを書いておくことができます。

また`SchemaExport`を利用し、O/Rマッパーで定義されているマッピング情報を元に、SQLite上に必要なテーブルを自動的に作った上でDBセッションをテストケースに渡す、ということも実装しています。

```csharp
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.PlatformAbstractions;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using System;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;

public class SQLiteInMemoryDatabaseFixture: IDisposable
{
    private static ISessionFactory sessionFactory;
    private static Configuration configuration;
    public static ISessionFactory SessionFactory {
        get
        {
            if (sessionFactory == null)
            {
                sessionFactory = Fluently.Configure()
                    .Database(
                        SQLiteConfiguration.Standard
                            .InMemory()
                            .ShowSql()
                        )
                    .Mappings(m =>
                    {
                        m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
                    })
                    .ExposeConfiguration(cfg => {
                        configuration = cfg;
                    })
                    .BuildSessionFactory();
            }
            return sessionFactory;
        }
    }

    public ISession GetSession()
    {
        ISession session = SessionFactory.OpenSession();

        var export = new SchemaExport(configuration);
        export.Execute(true, true, false, session.Connection, null);

        return session;
    }
    

    #region IDisposable Support
    private bool disposedValue = false; // 重複するDispose()呼び出しを検出する

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                if (sessionFactory != null)
                    sessionFactory.Dispose();
            }

            sessionFactory = null;
            configuration = null;

            disposedValue = true;
        }
    }

    //IDisposableを実装しておくと、xUnitがテスト終了時に呼び出してくれる
    // https://xunit.github.io/docs/shared-context.html#class-fixture
    public void Dispose()
    {
        Dispose(true);
    }
    #endregion

}

```


## テストケースでFixtureクラスを利用する

テストケースが`IClassFixture<TFixture>`を継承していると、テストケースのコンストラクタに`TFixture`のインスタンスが渡されます。
渡されたfixtureをフィールドに格納しておき、テストケース内から適宜利用する形になります。


```csharp

public class MyServiceTest: IClassFixture<SQLiteInMemoryDatabaseFixture>
{
    private readonly SQLiteInMemoryDatabaseFixture _fixture;

    public MyServiceTest(SQLiteInMemoryDatabaseFixture fixture)
    {
        _fixture = fixture;
    }

    [Fact]
    public void InterfaceImplimentationTest()
    {
        var session = _fixture.GetSession();
        var uowFactory = new NHibernateUnitOfWorkFactory(session: session);

        // テストケースが続く．．．．．．．
    }
}
```


## 利用したNuGetライブラリ

project.jsonで読み込んでいるライブラリはこんな感じです。

```json
"dependencies": {
    "NHibernate": "4.0.4.4000",
    "xunit": "2.1.0",
    "dotnet-test-xunit": "1.0.0-rc2-build10025",
    "xunit.runner.visualstudio": "2.1.0",
    "FluentNHibernate": "2.0.3",
    "FluentMigrator": "1.6.2",
    "FluentMigrator.Tools": "1.6.2",
    "System.Data.SQLite": "1.0.101",
    "System.Data.SQLite.Core": "1.0.101",
    "System.Data.SQLite.Linq": "1.0.101",
    "System.Data.SQLite.x64": "1.0.101"
}
```