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
When isolating a static method of a class, the instance constructor is no longer run when called from static member initialization or static constructor of the same class. Given this simple class:

public class CrazySingleton
{
   public static CrazySingleton Instance = new CrazySingleton();
   public CrazySingleton() { throw new Exception("Instance constructor says 'You shall not pass!'"); }
   public static int Echo(int num) { return num; }
}


And given the following test class:

[TestClass, Isolated] public class CrazySignletonTests
{
   [TestMethod] public void Test1()
   {
      CrazySingleton.Echo(7);
   }
      
   [TestMethod] public void Test2()
   {
      Isolate.WhenCalled(() => CrazySingleton.Echo(0)).WillReturn(7);
      CrazySingleton.Echo(7);
   }
}


When you run the first test by itself, it fails with a TypeInitializationException as you'd expect. When you run the second test by itself, it passes, though it shouldn't - it should throw the same TypeInitializationException as the first test.

My problem is that in the singleton class that I'm trying to test, the static constructor accesses a static method, which I want to isolate, and then constucts the singleton instance. Unfortunatly, if I fake the static method, the instance constructor no longer runs. I have been unable to find a workaround.

I'm using Isolator 6.2.5.0 with Visual Studio 2010 SP1 10.0.40219.1 SP1Rel and .NET Framework 4.0.30319 SP1Rel on Windows 7 Professional x64 SP1 (build 7601).

Thanks!
asked by allon.guralnek (10.6k points)

6 Answers

0 votes
Hi allon,

What you are seeing is actually a behavior of the CLR, regarding how static initialization works. In your example when test1 runs an exception is thrown during the static initialization. This causes the CLR to know that this static type is "broken". So, when test2 runs after test1 and it tries to access CrazySingleton, a type initialization is immediatly thrown.


When test2 runs alone the following happens:
1. The static constructor of CrazySingleton is called. Note if you want to avoid calling it use - Isolate.Fake.StaticConstructor API.
2. The instance constructor is called by the the static constructor.
3. Isolator "sees" the instance constructor as a call you want to fake and blocks it.

So, if you add Isolate.Fake.StaticConstructor<CrazySingleton>() in the beginning of both tests, you will get a consistent result.
answered by yoel (1.9k points)
0 votes
Isolator "sees" the instance constructor as a call you want to fake and blocks it.


Why? I never asked to fake any instance members of that class, let alone the instance constructor, so why does Isolator fake it?

I do not want to fake the static constructor, I want it to be called. I should be able to fake a single static method without anything else being faked - specifically the instance constructor. What does the instance constructor have to do with faking something static, anyway? I just want Isolator to fake only what I tell it to, not more than what I tell it. I could understand if it would fake less than what was asked due to technical restrictions (as with mscorlib types), but it doesn't make sense that it would fake more.

As for the "behavior of the CLR", I never said you should run both tests at the same time. In my post I always explicitly specified to run them separately. Of course if one test causes a TypeInitializationException then all other tests that attempt to use that type will also fail. The reason I put these two tests in the same test class is just for brevity's sake.
answered by allon.guralnek (10.6k points)
0 votes
Hi,

The reason that Isolator fakes the instance constructor is that that the Isolator is doing runtime analysis of the lambda inside WhenCalled() API.
In order to know what you want to fake the Isolator invokes the lambda and record the results.
What happens in the example you posted is:
1. The static constructor of CrazySingleton is invoked as a result of calling a static method.
2. The Isolator does not fake it.
3. The static constructor calls the instance constructor. This is because of a hard coded rule that let the static constructor run unless the user specifically calls Isoalte.Fake.StaticConstructor()
4. The default for instance constructor is to ignore it unless the explicitly wants to call it.

In order to get the same behavior in the tests use this:
[Test]
public void Test2()
{
    Isolate.Fake.Instance<CrazySingleton>(Members.ReturnRecursiveFakes, ConstructorWillBe.Called);
    Isolate.WhenCalled(() => CrazySingleton.Echo(0)).WillReturn(7);
    CrazySingleton.Echo(7);
}


Please let me know if it helps.
answered by ohad (35.4k points)
0 votes
Yes, the following workaround was successful:

Isolate.Fake.Instance<CrazySingleton>(Members.CallOriginal, ConstructorWillBe.Called);


Although normally such a call should be equivalent to a no-op, since that is the default behavior. And not using the return value of Fake.Instance is just odd. Now every test that touches that class requires two lines to fake a single method, plus a comment to explain what is going on. In addition, the workaround is non-intuitive, obscure and misleading, so I'd still definitely call this a bug. A bug that (so I hear) might be prohibitively expensive to fix and only solve a rarely encountered edge case, but a bug nonetheless. Put it somewhere in your bug database, so that maybe someday (perhaps if you switch to walking Expression<T> trees instead of profiler-based recording), you'll be able to fix it.

Thanks for your help!
answered by allon.guralnek (10.6k points)
0 votes
Hi Allon,
We had a discussion here about this scenario and yes we consider it as a bug. Isolator should not fake the instance constructor when the user asks to fake a static method.
About the expression walk you suggested - We actually tried that but it has some limitations that will not allow us to do everything we want.
answered by ohad (35.4k points)
0 votes
It seems the following workaround works for most of the tests, but not all of them:

Isolate.Fake.Instance<CrazySingleton>(Members.CallOriginal, ConstructorWillBe.Called);


For some tests, I must do the following in order for the instance constructor to be called (assuming CrazySinglton.Instance is a property and not a field like in my original example):

Isolate.WhenCalled(() => CrazySingleton.Instance).WillReturn(Isolate.Fake.Instance<CrazySingleton>(Members.CallOriginal, ConstructorWillBe.Called));


Just FYI.
answered by allon.guralnek (10.6k points)
...