Validating OccursOnce in Mocked Object

5 posts, 0 answers
  1. David
    David avatar
    5 posts
    Member since:
    Mar 2018

    Posted 03 Sep 2018 Link to this post

    I'm using NLog with the ILogger interface (and XUnit.net).  I want to verify that the Error() function is called, and I want to make the test as non-fragile as possible.  Today, the call within the function under test calls it _log.Error("format string", string, string), but that may change later and I don't want to rewrite the test every time that might get updated.  All I actually care about is that the error was logged rather than the specifics of how.

    I found another person's question that was similar (Here), but the suggestion there didn't work as I'd hoped (probably because his didn't use overloads).

    Today, this works:

    var log = Mock.Create<ILogger>();
    Mock.Arrange(() => log.Error(Arg.IsAny<string>(), Arg.IsAny<string>(), Arg.IsAny<string>())).IgnoreArguments().OccursOnce();
    Assert.Throws<ApplicationException>(() => myObject.Function(badParameter);
    Mock.Assert(log);

     

    What I'd prefer is something like what the other linked article suggested:

    var log = Mock.Create<ILogger>();
    Mock.Arrange(() => log.Error(string.Empty).IgnoreArguments().OccursOnce();
    Assert.Throws<ApplicationException>(() => myObject.Function(badParameter);
    Mock.Assert(log);

     

    I've also tried:

    Mock.Arrange(() => log.Error(Arg.IsAny<string>(), Arg.IsAny<object[]>()).IgnoreArguments().OccursOnce();

     

    The only one that works presently is the match with three string arguments. 

    I'm assuming this is due to all of the overloads that might match (see below).  In this situation, is there any way to accomplish a more generic way of ensuring that the Error function is called without having to make it tied to the argument count?

    Here's the most likely matches based on the overloads:
    Public method   Error(Object)
    Public method   Error(String,Object[])
    Public method   Error(String, String)
    Public method   Error(String, Object, Object)

    Here's the full specification.

     

     

  2. Ivo
    Admin
    Ivo avatar
    17 posts

    Posted 04 Sep 2018 Link to this post

    Hello David,

    Usually unit test is supposed to verify the contract which the particular peace of code is designed for. Enforcing a strict contract allows to detect any change in the code that breaks this contract at early stage. In my understanding, your question is about to verify that one of interface overloads gets called regardless of the signature, right? Imagine that you could be able to achieve that somehow, but what will happen if your arrangement wrongly matches the call which is a contract violation? Probably in this case you might be in trouble. That is way such behavior is not desired by design. Having all these things in mind, I would like to suggest some feasible solutions, just take a look the simplified sample below:

    public interface ILogger
    {
        void Error(object arg);
     
        void Error(string format, string arg);
     
        void Error(string format, params object[] args);
    }
     
    [TestMethod]
    public void TestMethod()
    {
        ILogger  sut = Mock.Create<ILogger>();
        Mock.Arrange(() => sut.Error(Arg.IsAny<object>())).IgnoreArguments().OccursOnce();
        Mock.Arrange(() => sut.Error(Arg.IsAny<string>(), Arg.IsAny<string>())).IgnoreArguments().OccursOnce();
        Mock.Arrange(() => sut.Error(Arg.IsAny<string>(), Arg.IsAny<object>())).IgnoreArguments().Occurs(3);
     
        // calls ILogger.Error(object)
        sut.Error("test" as object);
     
        // calls ILogger.Error(string, string)
        sut.Error("test {0}", "me");
     
        // call ILogger.Error(string, params object[])
        sut.Error("test");
        sut.Error("test {0} {0}", "me", "twice");
        sut.Error("test {0} {1} {2}", new[] { "me", "once", "again" });
     
        Mock.Assert(sut);
    }

    I hope it answers your question.

    Have a great day,
    Ivo
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  3. David
    David avatar
    5 posts
    Member since:
    Mar 2018

    Posted 04 Sep 2018 in reply to Ivo Link to this post

    To answer your question "verify that one of interface overloads gets called regardless of the signature, right?", yes.  I'm using the NLog library to log error messages and I'm not validating their code (it has its own test suite).  I'm attempting to verify that my function logs specific exception events.  The Error function that ultimately gets called may change if an additional bit of information needs to be included, I just need to verify that that the conditions and messages are being logged when the exception occurs.  If possible, I'd rather not have to rewrite the test if we add an additional parameter at a later date.  This function is a border call to a service API, and in many circumstances unrecoverable without intervention (credentials, server or network issues, etc.).  I just need to make sure that I am capturing the event to allow the user to react.

    I'll give your example a try and see if I can get it to work for my situation.

    Thanks

  4. Ivo
    Admin
    Ivo avatar
    17 posts

    Posted 07 Sep 2018 Link to this post

    Hello David,

    It would be nice if you share whether the proposed solution is good enough for you. In case of any further questions feel free to write us back.

    Regards,
    Ivo
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  5. David
    David avatar
    5 posts
    Member since:
    Mar 2018

    Posted 09 Sep 2018 in reply to Ivo Link to this post

    Won't work as it is.  The method signature isn't the same and I can't redefine the ILogger interface as that doesn't match the signature for my function.  Just going to have to live with it as is and update the test if the parameter list changes.
Back to Top