chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
0 votes
Hi,
I have some problem when I fake a class that inherit another:

    [TestClass]
    public class DummyClassTest
    {
        [TestMethod]
        [Isolated]
        public void TestNoFake()
        {
            var myClass = new MyClass();
            myClass.GetI();
        }

        [TestMethod]
        [Isolated]
        public void TestWithFake()
        {
            var fake = Isolate.Fake.Instance<MyClass>();
            Isolate.WhenCalled(() => fake.GetI()).WillReturn(3);
            fake.GetI();
        }
    }

    public class MyClass : MyBaseClass
    {
    }

    public class MyBaseClass
    {
        private static Logger s_logger = GlobalLogger.GetClassLogger();

        public int i = 0;

        public int GetI()
        {
            s_logger.SystemLogger.Write("asf");
            return i;
        }
    }

    public static class GlobalLogger
    {
        public static Logger GetClassLogger()
        {
            return new Logger();
        }
    }

    public class Logger
    {
        private SystemLogger m_SystemLogger = new SystemLogger();
        
        public SystemLogger SystemLogger
        {
            get { return m_SystemLogger; }
        }
    }

    public  class  SystemLogger
    {
        public void Write(string  s)
        {
            //I don't want to go there
            //throw new Exception();
        }
    }


To recreate the problem, TestWithFake must be run before TestNoFake.

The problem is that in TestNoFake, Logger.SystemLogger is null, which is impossible.
Something else strange, if I don't put the line:
Isolate.WhenCalled(() => fake.GetI()).WillReturn(3);

The test TestNoFake will pass (Logger.SystemLogger is not null).

Pierre-Luc
asked by pierre-luc (3.3k points)

6 Answers

0 votes
Hi,

The problem here is that during the recording stage in the line:
Isolate.WhenCalled(() => fake.GetI()).WillReturn(3);
The static constructor of MyBaseClass gets called which in its turn calls GlobalLogger.GetClassLogger() Isolator 'thinks' that the call to GlobalLogger.GetClassLogger() should be faked and returns a fake Logger object. This object gets cleaned when the test ends and that's why you get the exception.

To solve this you can tell the Isolator not fake the constructor of MyClass
like this:
[TestMethod]
[Isolated]
public void TestWithFake()
{
    var fake = Isolate.Fake.Instance<MyClass>(Members.ReturnRecursiveFakes, ConstructorWillBe.Called);    
    Isolate.WhenCalled(() => fake.GetI()).WillReturn(3);
    fake.GetI();
}


This will initiate the Logger correctly and the instance will 'live' in the next test.

Hope it helps.
answered by ohad (35.5k points)
0 votes
That's perfect.

Thanks
answered by pierre-luc (3.3k points)
0 votes
Ohad,

The suggestion to call the constructor on the faked type looks like a workaround to a real problem because by doing that the logger is no longer faked. Also in some situations it is not practical to call the constructor. Let me breakdown the problem one more time:

1- a class which has a parent class with a private static member of type Logger ie: "private static Logger s_logger = GlobalLogger.GetClassLogger();" is faked.
2- another class (ClassToTest) which also has a private static member of type Logger is instantiated.
3- a method is invoked on the ClassToTest instance; that method uses the s_logger private static member by attempting to access its SystemLogger property, but the SystemLogger property is null!

This is demonstrated by the code below:
 [TestClass]
    public class BaseClassStaticMemberInit
    {
        [TestMethod]
        [Isolated]
        public void TestClassWithPrivateStaticLogger()
        {
            var fake = Isolate.Fake.Instance<MyClass>();
            // following line will fake the private static member of MyBaseClass
            Isolate.WhenCalled(() => fake.GetI()).WillReturn(3);

            var target = new ClassToTest();
            // The following call will throw because s_logger.SystemLogger
            // is null in the target object!
            target.DoSomething();
        }

        public class ClassToTest
        {
            private static Logger s_logger = GlobalLogger.GetClassLogger();

            public void DoSomething()
            {
                s_logger.SystemLogger.Write("hello");
            }
        }

        public class MyClass : MyBaseClass
        {
            public MyClass(IDictionary<string> dict)
                :base(dict)
            {}
        }

        public class MyBaseClass
        {
            private static Logger s_logger = GlobalLogger.GetClassLogger();

            public MyBaseClass(IDictionary<string> dict)
            {
                int value;
                if (dict.TryGetValue("MyKey", out value))
                    i = value;
                else
                    throw new ArgumentException();
            }
            public int i = 0;

            public int GetI()
            {
                s_logger.SystemLogger.Write("asf");
                return i;
            }
        }

        public static class GlobalLogger
        {
            public static Logger GetClassLogger()
            {
                return new Logger();
            }
        }

        public class Logger
        {
            private SystemLogger m_SystemLogger = new SystemLogger();

            public SystemLogger SystemLogger
            {
                get { return m_SystemLogger; }
            }
        }

        public class SystemLogger
        {
            public void Write(string s)
            {
                //I don't want to go there 
                //throw new Exception(); 
            }
        } 
    }


By inserting the following line before faking MyClass we workaround the problem without having to call the constructor on the faked type.
Isolate.Fake.StaticConstructor(typeof (MyBaseClass));

But this workaround is not obvious because it is the constructor of the base class that we have to fake in order for the code in another class to behave properly.

The bottom line is that a recursive fake (s_logger in MyBaseClass) causes non-faked code in another class to be improperly initialized (s_logger in ClassToTest has its m_SystemLogger value to null). This looks like a bug, do you agree?


Do you agree this looks like a bug?
answered by patrick.gill@verint. (2.6k points)
0 votes
Hi Patrick,

Yes I agree there's a bug here.
In this case the user did not want to fake any Logger class but since the static constructor of MyClass result a call to GlobalLogger.GetClassLogger the Isolator fakes it.
Probably what we should do is ignore calls that are on the stack of static constructor when the user does not want fake statics.
answered by ohad (35.5k points)
0 votes
Hi Ohad!

Ignoring calls on the stack of static constructor when the user does not want to fake statics sounds like a good solution... it should avoid the problem exposed above.

Can this be done in an upcoming update?
answered by patrick.gill@verint. (2.6k points)
0 votes
Hi Patrick,

I added the bug to our list.
We'll evaluate the bugs that we need to fix next week and I'll bring it up to the team.
answered by ohad (35.5k points)
...