วิธีการทดสอบหน่วยด้วย ILogger ใน ASP.NET Core


130

นี่คือตัวควบคุมของฉัน:

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 รายการคือ a IDAOและILogger

และนี่คือคลาสทดสอบของฉันฉันใช้ xUnit เพื่อทดสอบและ Moq เพื่อสร้างการจำลองและต้นขั้วฉันสามารถเยาะเย้ยได้DAOง่าย แต่ด้วยความที่ILoggerฉันไม่รู้ว่าจะทำอย่างไรฉันจึงส่งโมฆะและแสดงความคิดเห็นเพื่อเรียกตัวควบคุมเข้าสู่ระบบ เมื่อทดสอบการทำงาน มีวิธีทดสอบ แต่ยังคงให้คนตัดไม้อยู่หรือไม่?

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

1
คุณสามารถใช้การเยาะเย้ยเป็นต้นขั้วได้ตามที่ Ilya แนะนำหากคุณไม่ได้พยายามทดสอบว่ามีการเรียกใช้วิธีการบันทึกจริงหรือไม่ หากเป็นเช่นนั้นการล้อเลียนคนตัดไม้จะไม่ได้ผลและคุณสามารถลองใช้วิธีต่างๆได้ ฉันได้เขียนบทความสั้น ๆ เกี่ยวกับวิธีการต่างๆ บทความรวมถึงrepo GitHub เต็มรูปแบบกับแต่ละตัวเลือกที่แตกต่างกัน ในท้ายที่สุดคำแนะนำของฉันคือให้ใช้อะแดปเตอร์ของคุณเองแทนที่จะทำงานโดยตรงกับประเภท ILogger <T> หากคุณจำเป็นต้องสามารถ
ssmith

ในฐานะที่เป็น @ssmith ILoggerบอกว่ามีปัญหาบางอย่างกับการตรวจสอบการโทรที่เกิดขึ้นจริง เขามีคำแนะนำที่ดีบางอย่างในบล็อกโพสต์ของเขาและฉันได้มาด้วยวิธีการแก้ปัญหาของฉันที่ดูเหมือนว่าจะแก้ปัญหาส่วนใหญ่ของปัญหาในคำตอบด้านล่าง
Ilya Chernomordik

คำตอบ:


142

เพียงแค่ล้อเลียนเช่นเดียวกับการพึ่งพาอื่น ๆ :

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.AbstractionsILogger<T>

นอกจากนี้คุณสามารถสร้างคนตัดไม้จริง:

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

var factory = serviceProvider.GetService<ILoggerFactory>();

var logger = factory.CreateLogger<BlogController>();

5
เพื่อล็อกเข้าสู่หน้าต่างเอาต์พุตดีบักให้เรียก AddDebug () บนโรงงาน: var factory = serviceProvider.GetService <ILoggerFactory> () .AddDebug ();
spottedmahn

3
ฉันพบว่าแนวทาง "คนตัดไม้ตัวจริง" มีประสิทธิภาพมากกว่า!
DanielV

1
ส่วนคนตัดไม้จริงยังใช้งานได้ดีสำหรับการทดสอบ LogConfiguration และ LogLevel ในสถานการณ์เฉพาะ
Martin Lottering

วิธีนี้จะอนุญาตเฉพาะต้นขั้ว แต่ไม่สามารถยืนยันการโทรได้ ฉันได้มาด้วยวิธีการแก้ปัญหาของฉันที่ดูเหมือนว่าจะแก้ปัญหาส่วนใหญ่ของปัญหาที่มีการยืนยันในคำตอบด้านล่าง
Ilya Chernomordik

102

อันที่จริงฉันพบว่าMicrosoft.Extensions.Logging.Abstractions.NullLogger<>ซึ่งดูเหมือนเป็นทางออกที่สมบูรณ์แบบ ติดตั้งแพคเกจMicrosoft.Extensions.Logging.Abstractionsจากนั้นทำตามตัวอย่างเพื่อกำหนดค่าและใช้งาน:

using Microsoft.Extensions.Logging;

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddSingleton<ILoggerFactory, NullLoggerFactory>();

    ...
}
using Microsoft.Extensions.Logging;

public class MyClass : IMyClass
{
    public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";

    private readonly ILogger<MyClass> logger;

    public MyClass(ILoggerFactory loggerFactory)
    {
        if (null == loggerFactory)
        {
            throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
        }

        this.logger = loggerFactory.CreateLogger<MyClass>();
    }
}

และการทดสอบหน่วย

//using Microsoft.VisualStudio.TestTools.UnitTesting;
//using Microsoft.Extensions.Logging;

[TestMethod]
public void SampleTest()
{
    ILoggerFactory doesntDoMuch = new Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory();
    IMyClass testItem = new MyClass(doesntDoMuch);
    Assert.IsNotNull(testItem);
}   

ดูเหมือนว่าจะใช้ได้กับ. NET Core 2.0 เท่านั้นไม่ใช่. NET Core 1.1
Thorkil Værge

3
@adospace ความคิดเห็นของคุณมีประโยชน์มากกว่าคำตอบ
johnny 5

คุณช่วยยกตัวอย่างได้ไหมว่าวิธีนี้ได้ผล? เมื่อทดสอบหน่วยฉันต้องการให้บันทึกปรากฏในหน้าต่างผลลัพธ์ฉันไม่แน่ใจว่าจะทำได้หรือไม่
J86

@adospace ควรจะไปใน startup.cs หรือไม่?
raklos

1
@raklos hum ไม่ควรใช้ในวิธีการเริ่มต้นในการทดสอบที่ ServiceCollection เป็นอินสแตนซ์
adospace

32

ใช้เครื่องตัดไม้แบบกำหนดเองที่ใช้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()
    {
    }
}

ใช้ใน unittests ของคุณเช่น

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

1
สวัสดี งานนี้ดีสำหรับฉัน ตอนนี้ฉันจะตรวจสอบหรือดูข้อมูลบันทึกของฉันได้อย่างไร
malik saifullah

ฉันกำลังเรียกใช้กรณีทดสอบหน่วยโดยตรงจาก VS. ฉันไม่มีคอนโซลสำหรับสิ่งนั้น
malik saifullah

1
@maliksaifullah ฉันใช้ resharper ให้ฉันตรวจสอบกับ
Jehof

1
@maliksaifullah TestExplorer ของ VS ให้ลิงค์เพื่อเปิดผลลัพธ์ของการทดสอบ เลือกการทดสอบของคุณใน TestExplorer และด้านล่างจะมีลิงค์
Jehof

1
ยอดเยี่ยมมากขอบคุณ! คำแนะนำสองสามข้อ: 1) ไม่จำเป็นต้องเป็นแบบทั่วไปเนื่องจากไม่ได้ใช้พารามิเตอร์ type การนำไปใช้ILoggerจะทำให้สามารถใช้งานได้ในวงกว้างมากขึ้น 2) BeginScopeไม่ควรส่งคืนตัวเองเนื่องจากนั่นหมายถึงวิธีการทดสอบใด ๆ ที่เริ่มต้นและสิ้นสุดขอบเขตระหว่างการดำเนินการจะกำจัดคนตัดไม้ แทนที่จะสร้างเอกชน "ดัมมี่" ชั้นซ้อนกันซึ่งการดำเนินการIDisposableและกลับไปอินสแตนซ์ที่ (แล้วเอาIDisposableจากXunitLogger)
Tobias J

27

สำหรับคำตอบ. net core 3 ที่ใช้ Moq

โชคดีที่stakxให้ดีวิธีแก้ปัญหา ดังนั้นฉันจึงโพสต์ด้วยความหวังว่าจะช่วยประหยัดเวลาให้กับผู้อื่นได้ (ใช้เวลาสักพักในการคิดออก):

 loggerMock.Verify(
                x => x.Log(
                    LogLevel.Information,
                    It.IsAny<EventId>(),
                    It.Is<It.IsAnyType>((o, t) => string.Equals("Index page say hello", o.ToString(), StringComparison.InvariantCultureIgnoreCase)),
                    It.IsAny<Exception>(),
                    (Func<It.IsAnyType, Exception, string>) It.IsAny<object>()),
                Times.Once);

คุณบันทึกวันของฉันไว้.. ขอบคุณ
KiddoDeveloper

15

การเพิ่ม 2 เซ็นต์ของฉันนี่เป็นวิธีการขยายตัวช่วยที่มักจะใส่ในคลาสตัวช่วยแบบคงที่:

static class MockHelper
{
    public static ISetup<ILogger<T>> MockLog<T>(this Mock<ILogger<T>> logger, LogLevel level)
    {
        return logger.Setup(x => x.Log(level, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()));
    }

    private static Expression<Action<ILogger<T>>> Verify<T>(LogLevel level)
    {
        return x => x.Log(level, 0, It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>());
    }

    public static void Verify<T>(this Mock<ILogger<T>> mock, LogLevel level, Times times)
    {
        mock.Verify(Verify<T>(level), times);
    }
}

จากนั้นคุณใช้มันดังนี้:

//Arrange
var logger = new Mock<ILogger<YourClass>>();
logger.MockLog(LogLevel.Warning)

//Act

//Assert
logger.Verify(LogLevel.Warning, Times.Once());

และแน่นอนว่าคุณสามารถขยายเพื่อเยาะเย้ยความคาดหวังใด ๆ ได้อย่างง่ายดาย (เช่นการเปิดเผยข้อความ ฯลฯ ... )


นี่เป็นวิธีการแก้ปัญหาที่สง่างามมาก
MichaelDotKnox

ฉันเห็นด้วยคำตอบนี้ดีมาก ฉันไม่เข้าใจว่าทำไมถึงไม่มีคะแนนโหวตมากมายขนาดนั้น
Farzad

1
Fab นี่คือเวอร์ชันสำหรับ non-generic ILogger: gist.github.com/timabell/d71ae82c6f3eaa5df26b147f9d3842eb
Tim Abell

เป็นไปได้ไหมที่จะสร้างการเยาะเย้ยเพื่อตรวจสอบสตริงที่เราส่งผ่านใน LogWarning? ตัวอย่างเช่น:It.Is<string>(s => s.Equals("A parameter is empty!"))
Serhat

สิ่งนี้ช่วยได้มาก ชิ้นส่วนที่ขาดหายไปสำหรับฉันคือฉันจะตั้งค่าการโทรกลับในการจำลองที่เขียนไปยังเอาต์พุต XUnit ได้อย่างไร ไม่เคยโทรกลับหาฉัน
flipdoubt

6

เป็นเรื่องง่ายเหมือนคำตอบอื่น ๆ ที่แนะนำให้ส่งต่อILoggerแต่ทันใดนั้นก็มีปัญหามากขึ้นในการตรวจสอบว่ามีการโทรหาคนตัดไม้จริงหรือไม่ เหตุผลก็คือการโทรส่วนใหญ่ไม่ได้เป็นของILoggerอินเทอร์เฟซ

ดังนั้นการโทรส่วนใหญ่จึงเป็นวิธีการขยายที่เรียกLogวิธีเดียวของอินเทอร์เฟซ เหตุผลที่ดูเหมือนว่าเป็นวิธีที่ง่ายกว่าในการใช้งานอินเทอร์เฟซหากคุณมีโอเวอร์โหลดเพียงอันเดียวและมีไม่มากจนเกินไปซึ่งใช้วิธีการเดียวกัน

แน่นอนว่าข้อเสียเปรียบก็คือการตรวจสอบว่ามีการโทรนั้นยากกว่าในทันทีเนื่องจากการโทรที่คุณควรตรวจสอบนั้นแตกต่างจากการโทรที่คุณโทรออกมาก มีวิธีการต่างๆในการแก้ไขปัญหานี้และฉันพบว่าวิธีการส่วนขยายที่กำหนดเองสำหรับการเยาะเย้ยกรอบงานจะทำให้ง่ายต่อการเขียน

นี่คือตัวอย่างของวิธีการที่ฉันได้สร้างขึ้นเพื่อใช้งานได้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");   

ดูเหมือนว่าคุณใช้วิธีนี้โดยตรงเคล็ดลับที่นี่คือวิธีการส่วนขยายของเราได้รับลำดับความสำคัญเนื่องจาก "ใกล้" ในเนมสเปซมากกว่าวิธีเดิมดังนั้นจึงจะใช้แทน

มันไม่ได้ให้สิ่งที่เราต้องการ 100% อย่างน่าเสียดายนั่นคือข้อความแสดงข้อผิดพลาดจะไม่ดีเท่าเพราะเราไม่ได้ตรวจสอบสตริงโดยตรง แต่ใช้แลมบ์ดาที่เกี่ยวข้องกับสตริง แต่ 95% นั้นดีกว่าไม่มีอะไรเลย :) นอกจากนี้ วิธีนี้จะทำให้รหัสทดสอบ

PS สำหรับขั้นต่ำหนึ่งสามารถใช้วิธีการของการเขียนวิธีขยายสำหรับการMock<ILogger<T>>ที่ไม่Verifyเพื่อให้บรรลุผลที่คล้ายกัน

PPS สิ่งนี้ใช้ไม่ได้ใน. Net Core 3 อีกต่อไปตรวจสอบรายละเอียดเพิ่มเติมในเธรดนี้: https://github.com/nsubstitute/NSubstitute/issues/597#issuecomment-573742574


ทำไมคุณถึงยืนยันการโทรของคนตัดไม้? พวกเขาไม่ได้เป็นส่วนหนึ่งของตรรกะทางธุรกิจ หากมีสิ่งไม่ดีเกิดขึ้นฉันควรตรวจสอบพฤติกรรมของโปรแกรมจริง (เช่นการเรียกใช้ตัวจัดการข้อผิดพลาดหรือการโยนข้อยกเว้น) มากกว่าการบันทึกข้อความ
Ilya Chumakov

1
ฉันคิดว่ามันค่อนข้างสำคัญที่จะต้องทดสอบเช่นกันอย่างน้อยก็ในบางกรณี ฉันเห็นหลายครั้งเกินไปที่โปรแกรมล้มเหลวโดยไม่โต้ตอบดังนั้นฉันคิดว่ามันสมเหตุสมผลแล้วที่จะตรวจสอบว่าการบันทึกเกิดขึ้นเมื่อมีข้อยกเว้นเกิดขึ้นเช่นและไม่เหมือนกับ "อย่างใดอย่างหนึ่งหรือ" แต่เป็นการทดสอบทั้งพฤติกรรมของโปรแกรมจริงและการบันทึก
Ilya Chernomordik

5

พูดถึงแล้วคุณสามารถล้อเลียนเป็นอินเทอร์เฟซอื่น ๆ

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อินเทอร์เฟซไม่ใช่วิธีการขยาย


5

การสร้างผลงานของ @ ivan-samygin และ @stakx ต่อไปนี้เป็นวิธีการส่วนขยายที่สามารถจับคู่กับ Exception และค่าบันทึกทั้งหมด (KeyValuePairs)

งานเหล่านี้ (บนเครื่องของฉัน;)) กับ. Net Core 3, Moq 4.13.0 และ Microsoft.Extensions.Logging.Abstractions 3.1.0

/// <summary>
/// Verifies that a Log call has been made, with the given LogLevel, Message and optional KeyValuePairs.
/// </summary>
/// <typeparam name="T">Type of the class for the logger.</typeparam>
/// <param name="loggerMock">The mocked logger class.</param>
/// <param name="expectedLogLevel">The LogLevel to verify.</param>
/// <param name="expectedMessage">The Message to verify.</param>
/// <param name="expectedValues">Zero or more KeyValuePairs to verify.</param>
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel expectedLogLevel, string expectedMessage, params KeyValuePair<string, object>[] expectedValues)
{
    loggerMock.Verify(mock => mock.Log(
        expectedLogLevel,
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((o, t) => MatchesLogValues(o, expectedMessage, expectedValues)),
        It.IsAny<Exception>(),
        It.IsAny<Func<object, Exception, string>>()
        )
    );
}

/// <summary>
/// Verifies that a Log call has been made, with LogLevel.Error, Message, given Exception and optional KeyValuePairs.
/// </summary>
/// <typeparam name="T">Type of the class for the logger.</typeparam>
/// <param name="loggerMock">The mocked logger class.</param>
/// <param name="expectedMessage">The Message to verify.</param>
/// <param name="expectedException">The Exception to verify.</param>
/// <param name="expectedValues">Zero or more KeyValuePairs to verify.</param>
public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, string expectedMessage, Exception expectedException, params KeyValuePair<string, object>[] expectedValues)
{
    loggerMock.Verify(logger => logger.Log(
        LogLevel.Error,
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((o, t) => MatchesLogValues(o, expectedMessage, expectedValues)),
        It.Is<Exception>(e => e == expectedException),
        It.Is<Func<It.IsAnyType, Exception, string>>((o, t) => true)
    ));
}

private static bool MatchesLogValues(object state, string expectedMessage, params KeyValuePair<string, object>[] expectedValues)
{
    const string messageKeyName = "{OriginalFormat}";

    var loggedValues = (IReadOnlyList<KeyValuePair<string, object>>)state;

    return loggedValues.Any(loggedValue => loggedValue.Key == messageKeyName && loggedValue.Value.ToString() == expectedMessage) &&
           expectedValues.All(expectedValue => loggedValues.Any(loggedValue => loggedValue.Key == expectedValue.Key && loggedValue.Value == expectedValue.Value));
}


1

การสร้างหุ่นจำลองILoggerไม่ได้มีประโยชน์มากสำหรับการทดสอบหน่วย นอกจากนี้คุณควรตรวจสอบว่ามีการโทรบันทึก คุณสามารถฉีดจำลองILoggerด้วยMoqแต่การยืนยันการโทรอาจเป็นเรื่องยุ่งยากเล็กน้อย บทความนี้เจาะลึกเกี่ยวกับการยืนยันด้วย Moq

นี่คือตัวอย่างง่ายๆจากบทความ:

_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));

ตรวจสอบว่ามีการบันทึกข้อความข้อมูล แต่ถ้าเราต้องการตรวจสอบข้อมูลที่ซับซ้อนมากขึ้นเกี่ยวกับข้อความเช่นเทมเพลตข้อความและคุณสมบัติที่ตั้งชื่อมันจะยุ่งยากกว่า:

_loggerMock.Verify
(
    l => l.Log
    (
        //Check the severity level
        LogLevel.Error,
        //This may or may not be relevant to your scenario
        It.IsAny<EventId>(),
        //This is the magical Moq code that exposes internal log processing from the extension methods
        It.Is<It.IsAnyType>((state, t) =>
            //This confirms that the correct log message was sent to the logger. {OriginalFormat} should match the value passed to the logger
            //Note: messages should be retrieved from a service that will probably store the strings in a resource file
            CheckValue(state, LogTest.ErrorMessage, "{OriginalFormat}") &&
            //This confirms that an argument with a key of "recordId" was sent with the correct value
            //In Application Insights, this will turn up in Custom Dimensions
            CheckValue(state, recordId, nameof(recordId))
    ),
    //Confirm the exception type
    It.IsAny<NotImplementedException>(),
    //Accept any valid Func here. The Func is specified by the extension methods
    (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),
    //Make sure the message was logged the correct number of times
    Times.Exactly(1)
);

ฉันแน่ใจว่าคุณสามารถทำเช่นเดียวกันกับกรอบการเยาะเย้ยอื่น ๆ ได้ แต่ILoggerอินเทอร์เฟซช่วยให้มั่นใจได้ว่ามันยาก


1
ฉันเห็นด้วยกับความเชื่อมั่นและอย่างที่คุณบอกว่ามันอาจจะยากสักหน่อยในการสร้างสำนวน ฉันมีปัญหาเดียวกันบ่อยครั้งดังนั้นเมื่อเร็ว ๆ นี้จึงรวบรวม Moq.Contrib.ExpressionBuilders เข้าสู่ระบบเพื่อให้อินเทอร์เฟซที่คล่องแคล่วซึ่งทำให้ถูกปากมากขึ้น
rgvlee

1

หากยังคงเกิดขึ้นจริง วิธีง่ายๆในการเข้าสู่ผลลัพธ์ในการทดสอบสำหรับ. net core> = 3

[Fact]
public void SomeTest()
{
    using var logFactory = LoggerFactory.Create(builder => builder.AddConsole());
    var logger = logFactory.CreateLogger<AccountController>();
    
    var controller = new SomeController(logger);

    var result = controller.SomeActionAsync(new Dto{ ... }).GetAwaiter().GetResult();
}


0

ฉันพยายามล้อเลียนอินเทอร์เฟซ Logger นั้นโดยใช้ NSubstitute (และล้มเหลวเนื่องจากArg.Any<T>()ต้องการพารามิเตอร์ type ซึ่งฉันไม่สามารถให้ได้) แต่ลงเอยด้วยการสร้างเครื่องบันทึกทดสอบ (คล้ายกับคำตอบของ @ jehof) ด้วยวิธีต่อไปนี้:

    internal sealed class TestLogger<T> : ILogger<T>, IDisposable
    {
        private readonly List<LoggedMessage> _messages = new List<LoggedMessage>();

        public IReadOnlyList<LoggedMessage> Messages => _messages;

        public void Dispose()
        {
        }

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

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

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            var message = formatter(state, exception);
            _messages.Add(new LoggedMessage(logLevel, eventId, exception, message));
        }

        public sealed class LoggedMessage
        {
            public LogLevel LogLevel { get; }
            public EventId EventId { get; }
            public Exception Exception { get; }
            public string Message { get; }

            public LoggedMessage(LogLevel logLevel, EventId eventId, Exception exception, string message)
            {
                LogLevel = logLevel;
                EventId = eventId;
                Exception = exception;
                Message = message;
            }
        }
    }

คุณสามารถเข้าถึงข้อความที่บันทึกไว้ทั้งหมดได้อย่างง่ายดายและยืนยันพารามิเตอร์ที่มีความหมายทั้งหมดที่มีให้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.