8

I need to test a method of type void, it just inserts a message on my LOG variable using a LOG framework (Serilog).

See the implementation example:

public class MyClass
{
    public MyClass(ILogger<IProcess> logger)
    {
        this.logger = logger;
    }

    private readonly ILogger logger;

    //...Anothers methods...

    public void LogInit(Guid processId, string folder, string[] args)
    {
        var myObject = new
        {
            ProcessId = processId,
            Folder = folder,
            Arguments = args
        };

        this.logger.LogWarning("{@myObject}", myObject);
    }
}

In this scenario, we would need to create a test for the LogInit (...)

Does this method really need to be tested? Or rather, does it make sense for it to exist?

I was reading something about:

"If your method has no side effects, and doesn’t return anything, then it’s not doing anything."

In this case, the method was only created "separately" to maintain the organization of the code and separate the responsibilities of the class a bit more.

That makes sense?

EDIT: @PeterBons

We do not have access to the messages registered in the Interface (logger), like: this.logger.GiveMeWarningLogs()...

Igor
  • 3,573
  • 4
  • 33
  • 55
  • 2
    It does have a side effect, it logs something. You could test that a warning is logged with the correct parameters when `LogInit` is called – Peter Bons Oct 20 '18 at 17:45
  • @PeterBons Yeah really! But in this case, the problem is Serilog because we do not have access to the messages registered in the Interface , understood? I have edited the question to better expose the "problem" of Serilog. – Igor Oct 20 '18 at 17:57
  • What do you mean? You can mock the ILogger interface and test it is properly called. What implementation it is (serilog in your case) is irrelevant for this test. – Peter Bons Oct 20 '18 at 18:01
  • 2
    Why are you even testing a private method? You should rather test the public interface of your class. All you should assert is that the logger's public method gets called, and you can achieve that by creating a mock logger, for which you then can define assertions. – k0pernikus Oct 20 '18 at 18:10
  • @k0pernikus I typed the wrong access modifier. – Igor Oct 20 '18 at 18:26
  • 1
    https://github.com/jet/callpolly is in F# but shows how you can make a custom sink to surface log entries to Seq/outputdebugstring/test output - I'm happy with my decision to do this, but then I'm building a library where the primary purpose is to provide very specific logging for a complex set of behaviors. In general I would not recommend writing tests for logging to tick a box. You might find [this to be a useful way to think about the whole thing](https://github.com/serilog/serilog/issues/1237#issuecomment-431276879). – Ruben Bartelink Oct 20 '18 at 18:29
  • 1
    You can also use serilog.sinks.observable, to trap things, but the basic way I buffer into a ConcurrentQueue works pretty well IME. Once again though: think lots and sleep on it before going down this road. – Ruben Bartelink Oct 20 '18 at 18:31
  • 1
    @RubenBartelink Thanks for the comments :). – Igor Oct 20 '18 at 18:40
  • Another option is to use InMemorySink, e.g. see https://stackoverflow.com/a/63798132/52277 – Michael Freidgeim Aug 28 '22 at 01:26

3 Answers3

11

If you're using Serilog, you can use the Test Correlator Sink to see what log events a method call produced.

[TestMethod]
public void A_test()
{
    var myClass = new MyClass(new LoggerConfiguration().WriteTo.TestCorrelator().CreateLogger());

    using (TestCorrelator.CreateContext())
    {
        myClass.LogInit();

        TestCorrelator.GetLogEventsFromCurrentContext()
            .Should().ContainSingle()
            .Which.MessageTemplate.Text
            .Should().Be("{@myObject}");
    }
}
gitbox
  • 803
  • 7
  • 14
3

You have to mock your logger and check whether LogWarning method was called. You can use Moq for this. Also if you want to test LogInit you need to make this public or internal with defining [InternalVisibleTo('someTestProjName')]

Test method will looks like this (xUnit):

public void ShouldCallLogWarning()
{
    var loggerMock = new Mock<ILogger>();
    loggerMock.Setup(_ => _.LogWarning(It.IsAny<string>(), It.IsAny<object>(), null);        

    var myClass = new MyClass(loggerMock.Object);

    //
    myClass.LogInit(Guid.NewGuid(), "folderPath", null)

    //
    _loggerMock.Verify(_ => _.LogWarning(It.IsAny<string>(), It.IsAny<string>(), null), Times.Once());
}
Igor
  • 3,573
  • 4
  • 33
  • 55
svoychik
  • 1,188
  • 1
  • 8
  • 19
-1

Your method is not returning anything so it would be harder to test if for sure. Right now the only thing your method does is prepare the logged object for the Serilog library. If you had more complicated logic for the creation of that object you could extract it into it's own method and have it return that log object then it would be easy to test that instead. The benefit of the method you have created is that it creates an extra layer so that if you decide to change the logging library you would only do it in one place.

Svilen
  • 44
  • 5
  • Mock frameworks have 'Verify' method, therefore you can verify the methods are called at least once or multiple. – alim Oct 20 '18 at 19:06