Why does Mock.Arrange In location (not order) matter

4 posts, 0 answers
  1. ben
    ben avatar
    2 posts
    Member since:
    Dec 2017

    Posted 21 Oct 2019 Link to this post

    I have a complex legacy application that I'm not allowed to refactor.   I am trying to write tests and don't want to repeat the same set up multiple times. I created a helper class which holds the mocks (TargetMocks) with methods to set up most of the logging arrange statements so I could focus the test on a specific aspect of the system under test.

    I've created an example NUnit test with a sample set of classes and Interfaces.   All but one of the tests work and I don't understand why the failing test fails. It has the same exact Mock.Arrange statements that are executed in the same order.   The only difference is the failing test has the Mock.Arrange statements in the test method while the working test has the Mock.Arrange methods in the TestMocks helper class.

    public class TargetMocks
    {
        public ILogger Logger { get; } = Mock.Create<ILogger>();
        public IUtility Util { get; } = Mock.Create<IUtility>();
        public StringBuilder LoggerBuilder {get;} = new StringBuilder();
        public void SetupMocks()
        {
            Mock.Arrange(() => Logger.Info(Arg.AnyString))
                .DoInstead((string msg) =>
                {
                    LoggerBuilder.AppendLine(msg);
                });
        }
        public Target CreateTarget()
        {
            var sut = new Target();
            Mock.Arrange(() => sut.Logger).Returns(Logger);
            Mock.Arrange(() => sut.Util).Returns(Util);
            return sut;
        }
        public Target CreateTargetAndSetupUtil()
        {
            var sut = new Target();
            Mock.Arrange(() => sut.Logger).Returns(Logger);
            Mock.Arrange(() => sut.Util).Returns(Util);
            //Mock.Arrange(() => mock.Util.ChangeState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();
            Mock.Arrange(() => Util.SaveState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();
     
            return sut;
        }
    }

     

    The following test fails

    [Test()]
    public void x3TestWidgetProcessUtilMethods_Test_TestUsesSeperateClassMocksAndCreatesSUT_But_Fails()
    {
        var expectedLog =
            "target message 1\r\n" +
            "Message 1\r\n" +
            "Message 3\r\n" +
            "target message 3\r\n";
     
        var mock = new TargetMocks();
        mock.SetupMocks();
        var sut = mock.CreateTarget();
        Mock.Arrange(() => mock.Util.ChangeState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();
        Mock.Arrange(() => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();
     
        sut.Process();
     
        var log = mock.LoggerBuilder.ToString();
     
        Assert.AreEqual(expectedLog, log, log);
        Mock.Assert(mock.Util);
    }

     

    But this test works.

    [Test()]
    public void x3aTestWidgetProcessUtilMethods_Test_TestUsesSeperateClassMocksAndCreatesSUTAndSetsUpUtilMock_WorksAsExpected()
    {
        var expectedLog =
            "target message 1\r\n" +
            "Message 1\r\n" +
            "Message 3\r\n" +
            "target message 3\r\n";
     
        var mock = new TargetMocks();
        mock.SetupMocks();
        var sut = mock.CreateTargetAndSetupUtil();
     
        sut.Process();
     
        var log = mock.LoggerBuilder.ToString();
     
        Assert.AreEqual(expectedLog, log, log);
        Mock.Assert(mock.Util);
    }

     

    • Why does the first test fail?

     

     

  2. Ivo
    Admin
    Ivo avatar
    27 posts

    Posted 23 Oct 2019 Link to this post

    Hello Ben,

    Using DebugView I was able to find the root cause of the issue, I recommend you to use it in the future to troubleshoot similar problems. Here is the CurrentState of the arrangements right before Mock.Assert statement:

    Elevated mocking: enabled
    Arrangements and expectations:
        Arrangement (id=0) (MyClassLibrary.Test.TargetMocks) => (MyClassLibrary.Test.TargetMocks).Logger.Info(Arg.AnyString):
        Arrangement (id=1) () => sut.Logger:
        Arrangement (id=2) () => sut.Util:
        Arrangement (id=3) () => mock.Util.ChangeState(Arg.AnyInt, Arg.AnyString):
            Unmet: Occurences must be in [1, any]; calls so far: 0.
        Arrangement (id=4) () => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString):
            Unmet: Occurences must be in [1, any]; calls so far: 0.
    Invocations:
        (ByRef (ILogger) Castle.Proxies.ILoggerProxy).Info("target message 1
    Message 1
    Message 3
    target message 3
    ") called 1 time; (signature: System.Object Info(System.String))
        (ByRef (IUtility) Castle.Proxies.IUtilityProxy).ChangeState((int) 1, "state1") called 1 time; (signature: Void ChangeState(Int32, System.String))
        (ByRef (IUtility) Castle.Proxies.IUtilityProxy).SaveState((int) 1, "state1") called 1 time; (signature: Void SaveState(Int32, System.String))
        (ByRef MyClassLibrary.Test.Target).get_Logger() called 1 time; (signature: MyClassLibrary.Test.ILogger get_Logger())
        (ByRef MyClassLibrary.Test.Target).get_Util() called 2 times; (signature: MyClassLibrary.Test.IUtility get_Util())
        (ByRef MyClassLibrary.Test.Target).Process() called 1 time; (signature: Void Process())
        (ByRef MyClassLibrary.Test.TargetMocks).<SetupMocks>b__9_1("target message 1
    Message 1
    Message 3
    target message 3
    ") called 1 time; (signature: Void <SetupMocks>b__9_1(System.String))
        (ByRef MyClassLibrary.Test.TargetMocks).CreateTarget() called 1 time; (signature: MyClassLibrary.Test.Target CreateTarget())
        (ByRef MyClassLibrary.Test.TargetMocks).get_Logger() called 1 time; (signature: MyClassLibrary.Test.ILogger get_Logger())
        (ByRef MyClassLibrary.Test.TargetMocks).get_LoggerBuilder() called 2 times; (signature: System.Text.StringBuilder get_LoggerBuilder())
        (ByRef MyClassLibrary.Test.TargetMocks).get_Util() called 1 time; (signature: MyClassLibrary.Test.IUtility get_Util())

     

    Why does it fail? The answer is not that trivial, generally it depends on which mock instance Mock.Arrange creates expectations. In this particular case the method overload Mock.Arrange(Expression<Action> expression) used to setup mock.Util.ChangeState and mock.Util.SaveState expectations creates implicitly a brand new instance of IUtility interface mock, instead of changing the existing one. As it shown above, the corresponding arrangements were not satisfied and the test failed. I am agree with you that this behavior is a kind of misleading, but this is the way of how it was designed. In order to make this test successful you have the following options:

    Mock.Arrange(() => mock.Util.ChangeState(Arg.AnyInt, Arg.AnyString)).IgnoreInstance().MustBeCalled();
    Mock.Arrange(() => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString)).IgnoreInstance().MustBeCalled();

    Mock.Arrange(mock.Util, u => u.ChangeState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();
    Mock.Arrange(mock.Util, u => u.SaveState(Arg.AnyInt, Arg.AnyString)).MustBeCalled();

    I hope the provided information helps. If you have any questions do not hesitate 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
  3. ben
    ben avatar
    2 posts
    Member since:
    Dec 2017

    Posted 23 Oct 2019 Link to this post

    Thanks, you answer was extremely helpful. My real world problem is much more complex, but your answer gives me the understanding and the tools (DebugView) to resolve my actual testing issues.

    A follow-up question:

    Please confirm or clarify: I believe you know it a different instance because one

    • Arrangement (id=2) reference is sut.Util vs.
    • the other (id=3) is mock.Util.

    Arrangements and expectations:
        Arrangement (id=0) (MyClassLibrary.Test.TargetMocks) => (MyClassLibrary.Test.TargetMocks).Logger.Info(Arg.AnyString):
        Arrangement (id=1) () => sut.Logger:
        Arrangement (id=2) () => sut.Util:
        Arrangement (id=3) () => mock.Util.ChangeState(Arg.AnyInt, Arg.AnyString):
            Unmet: Occurences must be in [1, any]; calls so far: 0.

     

  4. Ivo
    Admin
    Ivo avatar
    27 posts

    Posted 24 Oct 2019 Link to this post

    Hello Ben,

    Looking at CurrentView it would be hard to identify issues related to instances, it is more like a summary than a detailed presentation. If you need a proof that something wrong happened because of the instances, the right place to look into is FullTrace. Here are three different cases below (instance matching in bold):

    • Failed test

        Handling dispatch in repo #1 servicing Void x3TestWidgetProcessUtilMethods_Test_TestUsesSeperateClassMocksAndCreatesSUT_But_Fails()
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: No match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is not "ByRef (IUtility) Castle.Proxies.IUtilityProxy"
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: No match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is not "ByRef (IUtility) Castle.Proxies.IUtilityProxy"
        No arrangement chosen

    • Fixed test using IngnoreInstance()

        Handling dispatch in repo #1 servicing Void x3TestWidgetProcessUtilMethods_Test_TestUsesSeperateClassMocksAndCreatesSUT_But_Fails()
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: Match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is "any"
            arg 1: Match -> "(int) 1" is "IsAny<int>"
            arg 2: Match -> ""state1"" is "IsAny<string>"
            Found candidate arrangement (id=4) () => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString)
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: Match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is "any"
            arg 1: Match -> "(int) 1" is "IsAny<int>"
            arg 2: Match -> ""state1"" is "IsAny<string>"
            Found candidate arrangement (id=4) () => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString)
        Chosen arrangement (id=4) () => mock.Util.SaveState(Arg.AnyInt, Arg.AnyString)

    • Fixed test using Mock.Arrange<T>(T obj, Action<T> action) overload

        Handling dispatch in repo #1 servicing Void x3TestWidgetProcessUtilMethods_Test_TestUsesSeperateClassMocksAndCreatesSUT_But_Fails()
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: Match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is "ByRef (IUtility) Castle.Proxies.IUtilityProxy"
            arg 1: Match -> "(int) 1" is "IsAny<int>"
            arg 2: Match -> ""state1"" is "IsAny<string>"
            Found candidate arrangement (id=4)
        Inspect arrangements on Void SaveState(Int32, System.String) on MyClassLibrary.Test.IUtility
            this: Match -> "ByRef (IUtility) Castle.Proxies.IUtilityProxy" is "ByRef (IUtility) Castle.Proxies.IUtilityProxy"
            arg 1: Match -> "(int) 1" is "IsAny<int>"
            arg 2: Match -> ""state1"" is "IsAny<string>"
            Found candidate arrangement (id=4)
        Chosen arrangement (id=4)

    DebugView is a great tool indeed, honestly I use it quite often to handle tight cases or just to discover what is going on behind the scenes. I hope that provided information answers tour question. Please let me know if I can assist you any further.

    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
Back to Top