Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to mock NLog for unit test? #923

Closed
midexsoftware opened this issue Sep 22, 2015 · 17 comments
Closed

How to mock NLog for unit test? #923

midexsoftware opened this issue Sep 22, 2015 · 17 comments
Labels

Comments

@midexsoftware
Copy link

Hi,

I use NLog in most of my classes. All classes have log property:

ILogger log = LogManager.GetCurrentClassLogger();

then I use this property to write info to log.

If I want to write a unit test for a class that uses NLog, how do I substitute default logger that LogManager returns with my fake one?

One alternative is to pass ILogger parameter to all my constructors. This way I can pass real logger from the program and my mock logger that implements ILogger interface from the unit test. But is there a better way? I want to avoid writing ILogger parameters in all my constructors since most of my classes use logger.

Just as an example, MVVM Light has a class called Messenger. They have a function
Messenger.OverrideDefault(IMessenger fakeMessenger);
If you pass fake messenger into this function, then whichever class calls Messenger.Default, you would get fakeMessenger instead of the original class.

Is there something similar with NLog?
Maybe we can do something like this?
LogManager.SetCurrentClassLogger(ILogger log)

What is the best practice for testing classes that use NLog?

Thank you in advance

@304NotModified
Copy link
Member

If I want to write a unit test for a class that uses NLog, how do I substitute default logger that LogManager returns with my fake one?

I don't think it's possible, at least, not without some tricks. But why do you need this? You can stop all the loggers in your config (for the unit tests) - so the unit tests won't do any logging.

@304NotModified
Copy link
Member

Has you question being answered?

@midexsoftware
Copy link
Author

I was hoping for some sort of solution/example. But if the answer is, "it is not possible", then I guess my question is answered. Maybe developer of NLog can consider implementing it in the future.

At this time, I am passing ILogger as a parameter to all my classes that user NLog. This way I can Mock ILogger and pass my mock object instead. Then I can check if Logger methods were called as intended.

@304NotModified
Copy link
Member

Well, the answer is depends:

The key question, what are your trying to unit test? It's not clear to me.

  1. The NLog logger behavior of an (custom) Target, you can unit test that well, see unit tests in NLog.
  2. NLog itself - this is unit tested in NLog itself. No need to retest it.
  3. You code: well just do it, it should be "NLog free". You can remove/change the configuration of NLog in the unit tests.
  4. Combination of above: this is probably not a good unit test, as it doesn't test an unit. ;)

@midexsoftware
Copy link
Author

Hi,

I am testing my code, not nLog of course. So #3 above is what I need to test.

Can you let me know how to remove/change the configuration of NLog in the Unit Test? This will work for me when I simply want to ignore writing anything to log.

But, I am trying to test if method of NLog wes called. To do this, I have to pass NLog as a parameter to my class. This way I can mock it in my unit test. Example is bellow. The reason I brought up this question, is because lots of my classes use NLog. So I have to do add NLog as parameter in contractor for each of those classes. This works just fine and it is a pliable solution. However, I am working with MVVMLight at the same time. They have a class called Messenger. This is somewhat similar to NLog, you get default messenger object through static property of the Messenger class. They have a method called OverrideDefault. You can call it like so: Messenger.OverrideDefault(mockMessenger); Now instead of me massing IMessenger object to constructor of each class that uses Messenger, I can just do the overwrite in my unit test and I know that now my mockMessenger will be returned by the Messenger static method. I found it convenient way for unit testing. So if you are one of the NLog developer or anyone from NLog team is seeing this, this is just a suggestion that you might consider for the future.

[Test]
public void LoadAllListsWithEmptyDataTest()
{
var logger = Substitute.For();
var dataService = new DataService(logger);

        //throw exception so that it is caught and NLog write something
        dataService
            .When (x => x.GetPatients())
            .Do(x => { throw new Exception(); });

        //something has to be written to the log because exception was thrown
        var patients = dataService.GetPatients(); 

        //this checks that Error method was called 1 time
        logger.ReceivedWithAnyArgs(1).Error("does not matter"); 

    }

@304NotModified
Copy link
Member

If you don't like the side effect of NLog, just clear the configuration:

 //clear configuration
 LogManager.Configuration = new NLog.Config.LoggingConfiguration();

If you like to test your calls to NLog (your case)

 //init with empty configuration. Add one target and one rule
 var configuration = new NLog.Config.LoggingConfiguration();
 var memoryTarget = new MemoryTarget {Name = "mem"};

 configuration.AddTarget(memoryTarget);
 configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, memoryTarget));
 LogManager.Configuration = configuration;
 var logger = LogManager.GetLogger("test");
 logger.Info("ow noos");

 //read the logs here
 var logs = memoryTarget.Logs;

Result:

image

@304NotModified
Copy link
Member

Has your question being answered?

@304NotModified
Copy link
Member

Closing this due to inactivity. If you still experiencing problems, just let us know.

@craigbrett17
Copy link

This is an unsatisfactory answer. Mocking NLog shouldn't be as much of a pain as that.

@304NotModified: There is clearly a legit reason to want to return a pre-made ILogger in response to a GetCurrentClassLogger() call with a given type.

Say I want to test that my class is correctly logging exceptions, how would you suggest I do it? I know you want people to use some in-memory full logging NLog, but really I just want GetCurrentClassLogger() to return my mock.

Another alternative is to wrap LogManager in a LogManagerManager that behaves the way it should and rig that up to be testable.

@304NotModified 304NotModified self-assigned this Apr 19, 2017
@304NotModified
Copy link
Member

304NotModified commented Apr 24, 2017

This is an unsatisfactory answer. Mocking NLog shouldn't be as much of a pain as that.

Agree, it should be easy.

Say I want to test that my class is correctly logging exceptions, how would you suggest I do it? I know you want people to use some in-memory full logging NLog, but really I just want GetCurrentClassLogger() to return my mock.

Well changing the config is also kind of mocking. (change it to memorytarget, no code changes needed when logging) You could then test your write-to-NLog logic.

Or do you like to test your nlog.config? That sounds a bit odd to me.

@304NotModified 304NotModified removed their assignment Apr 24, 2017
@craigbrett17
Copy link

craigbrett17 commented Apr 27, 2017

No, unit tests should not have specific configurations. Those changes would come into all of your tests, which is not desirable. Separation of concerns and all that.

To keep those config changes from effecting the rest of your test code, you would either need to have a separate config and change configs to use that other config somehow for those specific tests, or else have a completely separate test project for your logging tests.

No, I do not want to test my log.config. I don't want to be within 20 miles of my log.config in test code. I just want LogManager.GetCurrentClassLogger() or similar to return me a Mock<ILogger> when I ask for a logger in my unit tests.

How would you even check that something has been logged with an in-memory log anyway? I didn't know you could read through old ILogger messages.

@304NotModified
Copy link
Member

What's the goal you are trying to achieve with the unit tests? (Preferable in a few sentences).

@304NotModified
Copy link
Member

With mocking you will replace behavior by configuration. This is possible with the NLog config as you could replace targets with your own targets. (or memory target by reading it Logs property.)

So NLog fully supports mocking

@craigbrett17
Copy link

No, I'm sorry, it doesn't.

Okay, I'll keep this simple, I want to test that on an exception, a suitable exception message is logged, or even that any error message is logged at all.

_mockLogger.Verify(l => l.Error(It.IsAny<string>()), Times.Once);

This is doable by wrapping your LogManager in another more friendly manager class, but isn't supported out of the box.

@304NotModified
Copy link
Member

Okay, I'll keep this simple, I want to test that on an exception, a suitable exception message is logged, or even that any error message is logged at all.

yes, you can do that with the memorytarget.

@moinoviimir
Copy link

If I may interject: I was also wondering whether I could verify that a system-under-test attempts to log an action and uses an appropriate log level for that. As you said, @304NotModified, that is doable with a memorytarget, but the setup needed for that is fairly lengthy and would complicate test setup. In any case, I would rather do behaviour verification instead of state verification here.

I could get a Facade going, but the case for supporting it out of the box seems reasonable to me.

@tbird2020
Copy link

I know its closed and all, but maybe the following could help someone finding this in the future.

Facade for encapsulating the memoylogger:

    public class LoggerMock
    {
        private MemoryTarget _memoryTarget;

        public IList<string> GetLogs => _memoryTarget.Logs;

        public  Logger GetLogger()
        {
            //init with empty configuration. Add one target and one rule
            _memoryTarget = new MemoryTarget { Name = "mem" };
            var configuration = new LoggingConfiguration();
            configuration.AddTarget(_memoryTarget);
            configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, _memoryTarget));
            LogManager.Configuration = configuration;
            return LogManager.GetLogger("test");
        }
    }

Usage:

var loggerMock = new LoggerMock();
classToTest.Logger = loggerMock;

classToTest.DoSomeThingThatLogs();

//asserts
var logs = loggerMock.GetLogs;
Assert.AreEqual(1, logs.Count);
Assert.IsTrue(logs.First().Contains("|WARN|")); //assert warning was logged
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants