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
Welcome to Typemock Community! Here you can ask and receive answers from other community members. If you liked or disliked an answer or thread: react with an up- or downvote.
0 votes
Hi,

We just discovered some really weird behavior when mocking a class. It took us some time to figure out what went wrong and as it turned out it is not really an issue with TypeMock. However, based on this we would like to suggest a TypeMock feature that would be a help in situations like this.

We have an inheritance hierarchy in which one of the base classes implements both a finalizer and the IDisposable interface (The bases classes are Windows GUI components). Subclassed from these are some 3rd party GUI components which in turn are used as base classes for some of our own GUI components.

Unfortunately, the 3rd party GUI classes do not consistently implement Dispose(bool) as they should. In one particular case they do not check the disposing flag and they don’t do any null guarding either. I.e. if the type is not fully constructed calling Dispose on it will result in a NullReferenceException being thrown.

As we mock one of these GUI components (including the constructor) the 3rd party Dispose will fail as it violates the required semantics for a Dispose-method.

In order to get around this we can mock Dispose on the mocked object. However, that will not do the trick, because although it will mock the explicit call to Dispose it can’t handle the implicit call caused when the finalizer of the base class runs. Of course we could mock Dispose twice, but since the second time only happens during finalization that prevents us from using MockManager.Verify() in our test method.

We have identified two ways to get around this. Either call GC.SuppressFinalize on the mocked instance or attach a delegate to MockMethodCalled and call SuppressFinalize if CalledMethodName equals “Dispose”.

The feature suggestion is to have a ExpectDisposeAndSuppressFinalize method which in addition to setting up an ExpectCall to Dispose on the mocked type would ensure that SupressFinalize is called in order to remove the mocked type from the finalization queue (and hence preventing another call to Dispose).

We are perfectly aware that this is not an issue with TypeMock but we have seen several examples of implementations of Dispose which do not adhere to the IDisposable pattern as described by Microsoft. We are reluctant to report this as a bug to the developers of the 3rd party GUI components, due to the fact that the problem is only seen because we use mocking. Even if we did report this our guess is that we may see similar problems with other types and hence it would be really nice to be able to control this aspect as well when mocking a type.

The code below is a simplified illustration of the problem:

public class MyComponent : MyComponentBase {
}

public class MyComponentBase : Form {
   Component c;
   public MyComponentBase() {
      c = new Component();
   }
   
   protected override void Dispose(bool disposing) {
      // The disposing flag is ignored and there's no null guard so this
      // will throw a NullReferenceException if the constructor didn't run
      c.Dispose();  
      base.Dispose(disposing);
   }
}

[TestMethod]
public void TestMethod() {
   Mock MyMock = MockManager.Mock(typeof(MyComponent), Constructor.Mocked);
   // This will handle the explicit call to Dispose (from "using"):
   MyMock.ExpectCall("Dispose"); 
   // Without this Dispose is called again during finalization:
   System.GC.SuppressFinalize(MyMock.MockedInstance); 
   using (MyComponent m = new MyComponent()) {
   }
   MockManager.Verify();
}


To see the problem remove the call to SuppressFinalize in the test method.

Regards,
Brian
asked by brian.rasmussen (7.2k points)

1 Answer

0 votes
Brian,
Thanks for the examples. We will implement a feature that will handle these cases.
answered by scott (32k points)
...