2

everyone,

I'm trying to do unit tests of my code, however I'm not able to get the ILoggerFactory to work

This is my unit test code (it may not be correct):

using NUnit.Framework;
using Microsoft.Extensions.Logging;
using System.Reflection;
using Moq;
using System;
using MyProgramVIP.Bots.Presenter;
using MyProgramVIP.Bots.Model;
using MyProgramVIP.Bots.Utils;

namespace MyProgramVIPTest
{
    public class TestsExample
    {
        [SetUp]
        public void Setup() {
            
        }

        [Test]
        public void TestExample1()
        {
            //Mocks
            var mockLogger = new Mock<ILogger<TicketPresenter>>();
            mockLogger.Setup(
                m => m.Log(
                    LogLevel.Information,
                    It.IsAny<EventId>(),
                    It.IsAny<object>(),
                    It.IsAny<Exception>(),
                    It.IsAny<Func<object, Exception, string>>()));

            var mockLoggerFactory = new Mock<ILoggerFactory>();
            mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);

            //Construción del modelo necesario para la prueba
            ConversationData conversationData = new ConversationData();
            conversationData.ticket = new Ticket();
            conversationData.response = new Response();

            //Invocación del método a probar
            TicketPresenter.getPutTicketMessage(conversationData);

            //Comprobación del funcionamineto
            Assert.AreEqual("ticketType", conversationData.response.cardIdResponse);
        }
    }
}

This is the code of the class I want to test (I've only left the lines of code that fail)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using MyProgramVIP.Bots.Interactor.Services;
using MyProgramVIP.Bots.Model;
using MyProgramVIP.Bots.Utils;

namespace MyProgramVIP.Bots.Presenter
{
    public class TicketPresenter
    {
        private static ILogger _logger = UtilsVIP.ApplicationLogging.CreateLogger(MethodBase.GetCurrentMethod().DeclaringType.Name);

        /// <summary>
        /// Función getPutTicketMessage: encargada de comenzar con el flujo de poner un ticket.
        /// </summary>
        /// <param name="conversationData"></param>
        public static void getPutTicketMessage(ConversationData conversationData)
        {
            try
            {
                //Here it crash.
                _logger.LogInformation("INIT getPutTicketMessage");

                //Code..........

                //Never gets here.
                _logger.LogInformation("ENDED getPutTicketMessage");
            }
            catch (Exception ex)
            {
                //Here it crash too.
                _logger.LogError("EXCEPTION getPutTicketMessage" + ex.ToString());
            }
        }
}

This is the code for the help class that's right where the error is:

using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using MyProgramVIP.Bots.Model;
using MyProgramVIP.Bots.Model.Elements;

namespace MyProgramVIP.Bots.Utils
{
    public class UtilsVIP
    {
        private static ILogger _logger = ApplicationLogging.CreateLogger("ILogger");

        //Other funtions...

        public static class ApplicationLogging
        {
            public static ILoggerFactory LoggerFactory { get; set; }
            public static ILogger CreateLogger<T>() => LoggerFactory.CreateLogger<T>();
            //Here LoggerFactory is null.
            public static ILogger CreateLogger(string categoryName) => LoggerFactory.CreateLogger("VIPLog: " + categoryName);

        }

        //Other funtions...
    }
}

Right on the line where it runs:

LoggerFactory.CreateLogger("VIPLog: " + categoryName);

LoggerFactory is null.

I've done a lot of research on the internet, however it all leads to non-static class information.

In case it's important, it's a Botframework project.

Any help would be appreciated.

Thanks in advance.

Stornu2
  • 2,284
  • 3
  • 27
  • 47
  • 2
    `ApplicationLogging.LoggerFactory = mockLoggerFactory.Object;` that's what you need to do – Chetan Aug 25 '20 at 08:27
  • 1
    A way you can make your code more SOLID is to avoid statics and try dependency injection - you can pass the ILogger needed to the constructor of whatever class that needs it. Check out a DI container like https://simpleinjector.org/ – Zorgarath Aug 25 '20 at 08:52
  • @ZorgarathThanks I will check the information in the link – Stornu2 Aug 25 '20 at 09:00

2 Answers2

4

Thanks a lot to the comment off @ChetanRanpariya the answer to my question was really simple:

using NUnit.Framework;
using Microsoft.Extensions.Logging;
using System.Reflection;
using Moq;
using System;
using MyProgramVIP.Bots.Presenter;
using MyProgramVIP.Bots.Model;
using MyProgramVIP.Bots.Utils;

namespace MyProgramVIPTest
{
    public class TestsExample
    {
        [SetUp]
        public void Setup() {
            
        }

        [Test]
        public void TestExample1()
        {
            //Mocks
            var mockLogger = new Mock<ILogger<TicketPresenter>>();
            mockLogger.Setup(
                m => m.Log(
                    LogLevel.Information,
                    It.IsAny<EventId>(),
                    It.IsAny<object>(),
                    It.IsAny<Exception>(),
                    It.IsAny<Func<object, Exception, string>>()));

            var mockLoggerFactory = new Mock<ILoggerFactory>();
            mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(() => mockLogger.Object);

            //Just add this, I guess is replacing the objet with the mock.
            UtilsVIP.ApplicationLogging.LoggerFactory = mockLoggerFactory.Object;
            
            //Construción del modelo necesario para la prueba
            ConversationData conversationData = new ConversationData();
            conversationData.ticket = new Ticket();
            conversationData.response = new Response();

            //Invocación del método a probar
            TicketPresenter.getPutTicketMessage(conversationData);

            //Comprobación del funcionamineto
            Assert.AreEqual("ticketType", conversationData.response.cardIdResponse);
        }
    }
}

Just add this:

UtilsVIP.ApplicationLogging.LoggerFactory = mockLoggerFactory.Object;

Thanks for the help.

Stornu2
  • 2,284
  • 3
  • 27
  • 47
1

Here in example on how to verify that a log was called on ILogger with Moq.

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

    private static bool CheckValue(object state, object expectedValue, string key)
    {
        var keyValuePairList = (IReadOnlyList<KeyValuePair<string, object>>)state;

        var actualValue = keyValuePairList.First(kvp => string.Compare(kvp.Key, key, StringComparison.Ordinal) == 0).Value;

        return expectedValue.Equals(actualValue);
    }

There is more context in this article.

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103