I have a class that receives an ILogger and I want to mock the LogInformation calls but this is an extension method. How do I make the appropiate setup call for this?
可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
由
翻译强力驱动
问题:
回答1:
ILogger is normally used thru extension methods, LogWarning, LogError, etc.
In my case I was interested in the LogWarning method which after looking at the code calls the Log method from ILogger. In order to mock it with Moq, this is what I ended up doing:
var list = new List<string>(); var logger = new Mock<ILogger>(); logger .Setup(l => l.Log<FormattedLogValues>(LogLevel.Warning, It.IsAny<EventId>(), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<FormattedLogValues, Exception, string>>())) .Callback( delegate (LogLevel logLevel, EventId eventId, FormattedLogValues state, Exception exception, Func<FormattedLogValues, Exception, string> formatter) { list.Add(state.ToString()); }); 回答2:
//Hi you can find the code with Moq.dll from the below link http://www.dotnetsurfers.com/blog/2010/04/02/getting-started-with-mocking-part-2-using-moq //Here Define Interfaces with models Logger,Product and ShoppingCart using System; using System.Diagnostics; using System.IO; namespace MOQSamples.Model { public interface ILogger { void Log(string text); } public class Logger : ILogger { public void Log(string text) { TextWriter tw = new StreamWriter(@"C:\temp\moq.log",false); tw.WriteLine(text); tw.Close(); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MOQSamples.Model { public class ShoppingCart { public ShoppingCart(ILogger logger) { this._logger = logger; } private ILogger _logger; public decimal Total { get; set; } public void AddProduct(IProduct product) { Total = Total + product.Price; if (_logger != null) _logger.Log(String.Format("Product {0} has been added.",product.Name)); } } } using System; using System.Data.SqlClient; namespace MOQSamples.Model { public interface IProduct { string Name { get; set; } decimal Price { get; set; } string GetProductCategory(); } public class Product : IProduct { public int ID {get;set;} public string Name {get; set;} public decimal Price { get { return GetPriceFromDatabase(); } set { throw new NotImplementedException(); } } public string GetProductCategory() { throw new NotImplementedException(); } private decimal GetPriceFromDatabase() { #region Retrieve Price from DB var conn = new SqlConnection("Server=WIN-V0L52BJTJS6; Database=MOQ; Integrated Security=SSPI;"); var query = "select Price from Product where ID =" + ID; var cmd = new SqlCommand(query, conn); conn.Open(); var price = (decimal)cmd.ExecuteScalar(); conn.Close(); return price; #endregion } } } //testing the logger using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Language; using MOQSamples.Model; namespace MOQSamples.Test { [TestClass] public class MOQDemoTests { private Mock<IProduct> _mockProduct; private Mock<ILogger> _mockLogger; [TestInitialize] public void InitializeTests() { _mockProduct = new Mock<IProduct>(); _mockLogger = new Mock<ILogger>(); } [TestMethod] public void Demo_Setup_Method() { //Show how a method call can be mocked and return fake data _mockProduct.Setup(m => m.GetProductCategory()).Returns("Test Category"); Console.WriteLine(_mockProduct.Object.GetProductCategory()); } [TestMethod] public void Demo_Setup_PropertyGet() { //Show how a property can be mocked and return fake data _mockProduct.SetupGet(m => m.Name).Returns("Product 1"); Console.WriteLine(_mockProduct.Object.Name); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void Demo_Setup_ThrowException() { //show how a mock can be used to throw exception _mockLogger.Setup(m => m.Log(It.Is<string>(p => p == null))). Throws(new ArgumentNullException()); _mockLogger.Object.Log(null); } [TestMethod] public void Demo_Validate_Params() { //show how mock can validate parameters _mockLogger.Setup(m => m.Log(It.IsRegex("[1-9]+"))). Callback(() => Console.WriteLine("Numbers passed")); _mockLogger.Object.Log("123"); } [TestMethod] public void Demo_Verify_Interactions() { _mockLogger.Object.Log("test"); _mockLogger.Verify(m => m.Log(It.Is<string>(s=>s=="test")),Times.Once()); } [TestMethod] public void Demo_Setup_CallBack() { //show how a mock can be used to invoke a callback int counter = 0; _mockLogger.Setup(m => m.Log(It.IsAny<String>())).Callback(() => counter++); _mockLogger.Object.Log("test"); _mockLogger.Object.Log("test2"); Console.WriteLine("Counter is " + counter); } } } 回答3:
This is how I workaround for Moq (v4.10.1) framework.
public static class TestHelper { public static Mock<ILogger<T>> GetMockedLoggerWithAutoSetup<T>() { var logger = new Mock<ILogger<T>>(); logger.Setup<object>(x => x.Log( It.IsAny<LogLevel>(), It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())); return logger; } public static void VerifyLogMessage<T>(Mock<ILogger<T>> mockedLogger, LogLevel logLevel, Func<string, bool> predicate, Func<Times> times) { mockedLogger.Verify(x => x.Log(logLevel, 0, It.Is<object>(p => predicate(p.ToString())), null, It.IsAny<Func<object, Exception, string>>()), times); } } --
public class Dummy { } [Fact] public void Should_Mock_Logger() { var logger = TestHelper.GetMockedLoggerWithAutoSetup<Dummy>(); logger.Object.LogInformation("test"); TestHelper.VerifyLogMessage<Dummy>(logger, LogLevel.Information, msg => msg == "test", Times.Once); } --
The thing is,
If I had chosen any other <TCustom> than <object> for logger.Setup(), it would fail on Verify step saying that 0 calls were made for x.Log<TCustom> and showing a call made to x.Log<object>. So I setup my generic logger to mock Log<object>(..) method instead.