Bagaimana untuk unit test dengan ILogger di ASP.NET Inti

Ini adalah controller:

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

Seperti yang anda lihat saya punya 2 ketergantungan, IDAO dan ILogger

Dan ini adalah tes saya di kelas, saya menggunakan xUnit untuk menguji dan Moq untuk membuat mock dan rintisan, aku bisa pura-pura DAO mudah, tapi dengan ILogger I don't tahu apa yang harus dilakukan jadi saya hanya lulus null dan komentar call log in controller ketika menjalankan tes. Apakah ada cara untuk menguji tapi masih tetap logger entah bagaimana ?

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());
    }
}
Mengomentari pertanyaan (2)
Larutan

Hanya pura-pura itu serta lain ketergantungan:

var mock = new Mock();
ILogger logger = mock.Object;

//or use this short equivalent 
logger = Mock.Of()

var controller = new BlogController(logger);

Anda mungkin akan perlu menginstal Microsoft.Ekstensi.Logging.Abstraksi paket untuk menggunakan ILogger<T>.

Selain itu anda dapat membuat nyata logger:

var serviceProvider = new ServiceCollection()
    .AddLogging()
    .BuildServiceProvider();

var factory = serviceProvider.GetService();

var logger = factory.CreateLogger();
Komentar (4)

Sebenarnya, aku've ditemukan Microsoft.Ekstensi.Logging.Abstraksi.NullLogger<> yang tampak seperti solusi yang sempurna.

Komentar (7)

Menggunakan custom logger yang menggunakan ITestOutputHelper (dari xunit) untuk menangkap output dan log. Berikut adalah sebuah contoh kecil yang hanya menulis negara untuk output.

public class XunitLogger : ILogger, IDisposable
{
    private ITestOutputHelper _output;

    public XunitLogger(ITestOutputHelper output)
    {
        _output = output;
    }
    public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
    {
        _output.WriteLine(state.ToString());
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public IDisposable BeginScope(TState state)
    {
        return this;
    }

    public void Dispose()
    {
    }
}

Menggunakannya dalam unittests seperti

public class BlogControllerTest
{
  private XunitLogger _logger;

  public BlogControllerTest(ITestOutputHelper output){
    _logger = new XunitLogger(output);
  }

  [Fact]
  public void Index_ReturnAViewResult_WithAListOfBlog()
  {
    var mockRepo = new Mock();
    mockRepo.Setup(repo => repo.GetMany(null)).Returns(GetListBlog());
    var controller = new BlogController(_logger,mockRepo.Object);
    // rest
  }
}
Komentar (5)

Menambahkan saya 2 sen, Ini adalah penolong metode penyuluhan biasanya dimasukkan ke dalam statis kelas helper: `` statis kelas MockHelper { public static ISetup<ILogger> MockLog(Mock<ILogger> logger, LogLevel tingkat) { kembali logger.Setup(x => x.Log(level Itu.IsAny(), Hal.IsAny<objek>(), Hal.IsAny(), Hal.IsAny<Func<objek, Pengecualian, string>>())); }

pribadi statis Ekspresi<Action<ILogger>> Verifikasi(LogLevel tingkat) { kembali x => x.Log(level, 0, Hal.IsAny<objek>(), Hal.IsAny(), Hal.IsAny<Func<objek, Pengecualian, string>>()); }

public static void Verifikasi(Mock<ILogger> pura-pura, LogLevel tingkat, Kali kali) { mengejek.Memverifikasi(Verify(tingkat), kali); } } Kemudian, anda dapat menggunakannya seperti ini: //Mengatur var logger = new Mock<ILogger>(); logger.MockLog(LogLevel.Warning)

//Bertindak

//Menyatakan logger.Verifikasi(LogLevel.Peringatan, Kali.Setelah()); `` Dan tentu saja anda dapat dengan mudah memperpanjang mengejek harapan (yaitu expection, pesan, dll ...)

Komentar (2)

Sudah disebutkan, anda bisa pura-pura itu sebagai antarmuka lainnya.

var logger = new Mock();

Sejauh ini baik-baik.

Hal yang baik adalah bahwa anda dapat menggunakan Moq ke pastikan bahwa panggilan tertentu yang telah dilakukan. Untuk contoh di sini saya periksa log telah disebut dengan Pengecualian.

logger.Verify(m => m.Log(It.Is(l => l == LogLevel.Information), 0,
            It.IsAny(), It.IsAny(), It.IsAny()));

Ketika menggunakan Verifikasi intinya adalah untuk melakukan itu melawan real Log metode `ILooger antarmuka dan bukan metode ekstensi.

Komentar (0)

Hal ini mudah karena jawaban yang lain menyarankan untuk melewati pura-pura ILogger, tapi itu tiba-tiba menjadi jauh lebih bermasalah untuk memverifikasi bahwa panggilan benar-benar dibuat untuk logger. Alasannya adalah bahwa sebagian besar panggilan tidak benar-benar milik ILogger antarmuka itu sendiri.

Sehingga semua panggilan adalah metode ekstensi panggilan itu hanya Log metode antarmuka. Alasan itu tampaknya adalah bahwa hal itu's cara yang lebih mudah untuk membuat implementasi antarmuka jika anda memiliki hanya satu dan tidak banyak overloads yang bermuara pada metode yang sama.

Kelemahan ini tentu saja bahwa itu adalah tiba-tiba jauh lebih sulit untuk memverifikasi bahwa panggilan telah dibuat sejak panggilan anda harus memverifikasi sangat berbeda dari panggilan yang anda buat. Ada beberapa pendekatan yang berbeda untuk bekerja di sekitar ini, dan saya telah menemukan bahwa ekstensi kustom metode untuk mengejek framework akan membuat lebih mudah untuk menulis.

Berikut ini adalah contoh dari metode yang saya telah dibuat untuk bekerja dengan NSubstitute:

public static class LoggerTestingExtensions
{
    public static void LogError(this ILogger logger, string message)
    {
        logger.Log(
            LogLevel.Error,
            0,
            Arg.Is(v => v.ToString() == message),
            Arg.Any(),
            Arg.Any());
    }

}

Dan ini adalah bagaimana hal itu dapat digunakan:

_logger.Received(1).LogError("Something bad happened");   

Hal ini terlihat persis seperti jika anda menggunakan metode langsung, kuncinya di sini adalah bahwa metode penyuluhan mendapat prioritas karena itu's "lebih" di namespaces daripada yang asli, sehingga akan digunakan sebagai gantinya.

Itu tidak memberikan sayangnya 100% apa yang kita inginkan, yaitu pesan kesalahan tidak akan menjadi baik, karena kita don't cek langsung pada string melainkan pada lambda yang melibatkan string, tapi 95% lebih baik dari apa-apa :) Selain itu pendekatan ini akan membuat kode tes

P. S. Untuk Moq satu dapat menggunakan pendekatan penulisan perpanjangan metode untuk Mengejek<ILogger<T>> yang melakukan Verifikasi untuk mencapai hasil yang sama.

Komentar (2)

Dan ketika menggunakan StructureMap / Lamar:

var c = new Container(_ =>
{
    _.For(typeof(ILogger)).Use(typeof(NullLogger));
});

Docs:

Komentar (0)

Untuk .net core 3 jawaban yang menggunakan Moq

tidak lagi bekerja karena perubahan yang dijelaskan dalam masalah Tkondisi di ILogger.Log digunakan untuk menjadi objek, sekarang FormattedLogValues

Lickily stakx yang disediakan bagus pemecahan masalah. Jadi saya'm postingan ini di harapkan dapat menghemat waktu untuk orang lain (butuh beberapa saat untuk mencari hal-hal yang keluar):

loggerMock.Verifikasi( x => x.Log( LogLevel.Informasi, Itu.IsAny<Pemberi>(), Itu.Adalah<Hal.IsAnyType>((o, t) => string.Sama dengan("halaman Indeks say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)), Itu.IsAny<Pengecualian>(), (Func<Hal.IsAnyType, Pengecualian, string>) Itu.IsAny<objek>()), Kali.Sekali);

Komentar (0)