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?