新しもの好きプログラマの耳より情報ブログ

仕事でもあるプログラミングについて役に立ちそうな情報を発信していこうというブログです。役に立たなそうな情報はfacebookで。

C#のUnitTestで、ILogger<T>のモックを簡単に作る方法(GenericHostで使える)

概要

UnitTest、書いてますか?

GenericHostやWebHostを使っている場合、テストコードを書くためには、必ずILogger<T>のモックが必要になると思います。これを手軽に作成する方法を書きます。

最初に結論まとめ

こんな感じの共通クラスでCreateLogger<T>メソッドを用意しておいて呼び出すことで、コンソール出力するILoggerを簡単に作れます。あとはこれを、コンストラクタ等のDI部分へ渡せばOKです。

internal class ILoggerToConsole
{
    private static readonly ILoggerFactory _factory = LoggerFactory.Create(builder =>
    {
        builder
            .AddConsole()
            .SetMinimumLevel(LogLevel.Trace);
    });

    public static ILogger<T> CreateLogger<T>()
    {
        return _factory.CreateLogger<T>();
    }

}

説明

そろそろ、GenericHostを使う人も増えてきたでしょうか。ASP.NET系のWebアプリならばWebHostをたぶん最初から使っていますね。それらでUnitTestを書こうとすると、最初に悩みどころになるのは、「ILogger<T>に何を渡そうか」ではないでしょうか。

Moqを使っているならば、ILoggerのインターフェースを満たすモックを作れば良い。それはその通りです。しかし、メソッドも多いし自力で書くのはちょっと面倒です。

NUnitの場合、ILoggerの内容をどこへ出力するモックを書きたいかというと、たぶんコンソール出力になると思います。

それならば、コンソール出力をする本物のロガーを作成してしまった方が、実のところモック作成よりも手軽です。その方法を書きます。

まず、どこへ何を出力するロガーを作るのかを決めて、ILoggerFactoryを作成します。

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace);
});

こんな感じです。ここでは、「コンソールに出力する」「出力対象の最小レベルをTraceとする」という設定です。

必要なのはILogger<T>で、Tは対象のクラスによって変わりますね。それに合わせ、次のようにインスタンスを作成します。作成したら、対象のクラスへ渡せばOKです。

var logger = loggerFactory.CreateLogger<クラス名>();

クラスごと使い方を例示すると、こんな感じです。

class TestTarget(ILogger<TestTarget> _logger){
}

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace);
});
var logger = loggerFactory.CreateLogger<TestTarget>();
var instance = new TestTarget(logger);

はい、これだけです!これだけで、コンソールへのログ出力をしてくれるILogger<T>のモックのできあがりです。(モックというか、本物ですが)

LoggerFactoryは使い回せるので、次のような共通化したクラスを作っておいて、必要な時にCreateLogger<T>を呼び出すと、使いやすいと思います。

internal static class ILoggerToConsole
{
    private static readonly ILoggerFactory _factory = LoggerFactory.Create(builder =>
    {
        builder
            .AddConsole()
            .SetMinimumLevel(LogLevel.Trace);
    });

    public static ILogger<T> CreateLogger<T>()
    {
        return _factory.CreateLogger<T>();
    }

}

さっきと同じようにクラスでの使い方を例示すると、こんな感じです。

class TestTarget(ILogger<TestTarget> _logger){
}

var logger = ILoggerToConsole.CreateLogger<TestTarget>();
var instance = new TestTarget(logger);

ちなみにコンソール出力がいらない場合は

コンソール出力するロガーを作って渡す方法を紹介しましたが、コンソール出力も不要な場合は実はもっと簡単な「NullLogger」という方法があります。そういう組み込みの型があるので、newするだけで使えます。

こんな使い方になります。

class TestTarget(ILogger<TestTarget> _logger){
}

var logger = new NullLogger<TestTarget>();
var instance = new TestTarget(logger);

まとめ

UnitTestのコードを頑張って書こうとしても、最初に「ロガーのインスタンスはどうしようか」で引っかかって挫折してしまってはもったいない。そういう問題はこの記事の定型コードで解決して、どんどんUnitTestのコードを書いていきましょう!