これは私のコントローラーです:
public class BlogController : Controller
{
private IDAO<Blog> _blogDAO;
private readonly ILogger<BlogController> _logger;
public BlogController(ILogger<BlogController> logger, IDAO<Blog> blogDAO)
{
this._blogDAO = blogDAO;
this._logger = logger;
}
public IActionResult Index()
{
var blogs = this._blogDAO.GetMany();
this._logger.LogInformation("Index page say hello", new object[0]);
return View(blogs);
}
}
ご覧のとおり、2つの依存関係、IDAO
とILogger
があります
これは私のテストクラスです。xUnitを使用してテストし、Moqを使用してモックとスタブを作成します。DAO
を簡単にモックできますが、ILogger
を使用すると、 nullを指定し、テストの実行時にログインコントローラーへの呼び出しをコメント化します。テストする方法はありますが、それでもロガーを保持しますか?
public class BlogControllerTest
{
[Fact]
public void Index_ReturnAViewResult_WithAListOfBlog()
{
var mockRepo = new Mock<IDAO<Blog>>();
mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
var controller = new BlogController(null,mockRepo.Object);
var result = controller.Index();
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsAssignableFrom<IEnumerable<Blog>>(viewResult.ViewData.Model);
Assert.Equal(2, model.Count());
}
}
他の依存関係と同様にそれをモックするだけです:
var mock = new Mock<ILogger<BlogController>>();
ILogger<BlogController> logger = mock.Object;
//or use this short equivalent
logger = Mock.Of<ILogger<BlogController>>()
var controller = new BlogController(logger);
Microsoft.Extensions.Logging.Abstractions
を使用するには、おそらくILogger<T>
パッケージをインストールする必要があります。
さらに、実際のロガーを作成できます。
var serviceProvider = new ServiceCollection()
.AddLogging()
.BuildServiceProvider();
var factory = serviceProvider.GetService<ILoggerFactory>();
var logger = factory.CreateLogger<BlogController>();
実際、完璧なソリューションのように見えるMicrosoft.Extensions.Logging.Abstractions.NullLogger<>
を見つけました。
ITestOutputHelper
(xunitから)を使用して出力とログをキャプチャするカスタムロガーを使用します。以下は、state
のみを出力に書き込む小さなサンプルです。
public class XunitLogger<T> : ILogger<T>, IDisposable
{
private ITestOutputHelper _output;
public XunitLogger(ITestOutputHelper output)
{
_output = output;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_output.WriteLine(state.ToString());
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public IDisposable BeginScope<TState>(TState state)
{
return this;
}
public void Dispose()
{
}
}
あなたのユニットテストでそれを使用してください
public class BlogControllerTest
{
private XunitLogger<BlogController> _logger;
public BlogControllerTest(ITestOutputHelper output){
_logger = new XunitLogger<BlogController>(output);
}
[Fact]
public void Index_ReturnAViewResult_WithAListOfBlog()
{
var mockRepo = new Mock<IDAO<Blog>>();
mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
var controller = new BlogController(_logger,mockRepo.Object);
// rest
}
}
他の答えがモックILogger
を渡すことを示唆しているように簡単ですが、実際にロガーへの呼び出しが行われたことを確認することは突然はるかに問題になります。その理由は、ほとんどの呼び出しが実際にはILogger
インターフェース自体に属していないためです。
そのため、ほとんどの呼び出しは、インターフェイスのLog
メソッドのみを呼び出す拡張メソッドです。理由は、同じメソッドに要約されるオーバーロードが1つだけであり、あまり多くない場合、インターフェイスの実装を作成する方が簡単だからです。
欠点は、もちろん、確認する必要のある呼び出しが行った呼び出しとは非常に異なるため、呼び出しが行われたことを確認することが突然難しくなることです。これを回避するためのいくつかの異なるアプローチがあり、フレームワークをモック化するためのカスタム拡張メソッドを使用すると簡単に記述できることがわかりました。
NSubstitute
を操作するために作成したメソッドの例を次に示します。
public static class LoggerTestingExtensions
{
public static void LogError(this ILogger logger, string message)
{
logger.Log(
LogLevel.Error,
0,
Arg.Is<FormattedLogValues>(v => v.ToString() == message),
Arg.Any<Exception>(),
Arg.Any<Func<object, Exception, string>>());
}
}
そして、これはそれがどのように使われることができるかです:
_logger.Received(1).LogError("Something bad happened");
メソッドを直接使用した場合とまったく同じように見えますが、ここでの秘theは、拡張メソッドが元の名前空間よりも名前空間で「近い」ため優先されるため、代わりに使用されます。
残念なことに、100%を提供するわけではありません。つまり、文字列を直接チェックするのではなく、文字列を含むラムダをチェックするため、エラーメッセージはそれほど良くありませんが、95%は何もないよりも優れています:)このアプローチはテストコードを作成します
追伸Moqでは、Verify
を実行するMock<ILogger<T>>
の拡張メソッドを記述するアプローチを使用して、同様の結果を達成できます。
既に他のインターフェイスとしてモックできると述べました。
var logger = new Mock<ILogger<QueuedHostedService>>();
ここまでは順調ですね。
嬉しいことに、Moq
を使用して特定の呼び出しが実行されたことを確認できます。たとえば、ここで特定のException
でログが呼び出されたことを確認します。
logger.Verify(m => m.Log(It.Is<LogLevel>(l => l == LogLevel.Information), 0,
It.IsAny<object>(), It.IsAny<TaskCanceledException>(), It.IsAny<Func<object, Exception, string>>()));
Verify
を使用するときのポイントは、拡張メソッドではなく、Log
インターフェイスからの実際のILooger
メソッドに対して実行することです。
そして、StructureMap/Lamarを使用する場合:
var c = new Container(_ =>
{
_.For(typeof(ILogger<>)).Use(typeof(NullLogger<>));
});
ドキュメント: