이것은 내 컨트롤러입니다.
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);
}
}
보시다시피 두 가지 종속성, a IDAO
및 aILogger
그리고 이것은 내 테스트 클래스입니다. xUnit을 사용하여 테스트하고 Moq를 사용하여 mock 및 stub을 만듭니다. DAO
쉽게 mock 할 수 있지만 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<>
완벽한 솔루션처럼 보이는 것을 찾았습니다 . 패키지를 설치 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);
}
답변
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
}
}
답변
Moq를 사용하는 .net 코어 3 답변의 경우
- https://stackoverflow.com/a/54646657/2164198
- https://stackoverflow.com/a/54809607/2164198
-
https://stackoverflow.com/a/56728528/2164198
ILogger.Log 의 문제 TState에 설명 된 변경으로 인해 더 이상 작동하지 않습니다. 이전에는 개체 였지만 이제는 FormattedLogValues입니다.
운 좋게도 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);
답변
내 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());
물론 기대치 (예 : 기대치, 메시지 등)를 조롱하기 위해 쉽게 확장 할 수 있습니다.
답변
다른 답변이 mock을 전달하도록 제안하는 것처럼 쉽지만 ILogger
실제로 logger에 대한 호출이 이루어 졌는지 확인하는 것이 갑자기 훨씬 더 문제가됩니다. 그 이유는 대부분의 호출이 실제로 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 For Moq Mock<ILogger<T>>
는 Verify
유사한 결과를 얻기 위해 확장 방법을 작성하는 접근 방식을 사용할 수 있습니다 .
PPS 이것은 .Net Core 3에서 더 이상 작동하지 않습니다. 자세한 내용은이 스레드를 확인하십시오 : https://github.com/nsubstitute/NSubstitute/issues/597#issuecomment-573742574
답변
이미 언급했듯이 다른 인터페이스처럼 조롱 할 수 있습니다.
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
입니다.