Faking ArcObjects with Typemock Isolator?

2058
2
11-13-2012 05:52 AM
FridjofSchmidt
Occasional Contributor
Hi all,

While diving into unit testing and isolation frameworks, I wanted to try Typemock Isolator to write unit tests for some legacy ArcObjects code. One of the main characteristics of good unit tests is that they test logical units in isolation, i.e., replacing dependencies with fakes. Typemock Isolator seems to have some advanced capabilities for this purpose.

In the production code, there are numerous occurrences of ArcObjects classes being instantiated, such as:
private void doSomething()
{
  ...
  IFields fields = new FieldsClass();
  this.doSomethingElse(fields);
  ...
}

What I basically want to do is to write tests in which I can test the logic of the production code units without accessing any real ArcObjects instances, and also without having to make any changes to the production code before writing tests.

However, there are several issues. First of all, even if I want to fake an ArcObjects class in Typemock Isolator, I need an ArcObjects license. If no license was initialized, the first line of the following method will fail with a COMException.
[Test]
public void SwapAllInstances_OfFieldsClass_SwapsFieldsClassCreatedWithNew()
{
    // Arrange
    FieldsClass fields = Isolate.Fake.Instance<FieldsClass>();
    Isolate.Swap.AllInstances<FieldsClass>().With(fields);

    // Act
    IFields newFields = new FieldsClass();

    // Assert
    Assert.That(newFields, Is.Not.InstanceOf<FieldsClass>());
}

This is actually against the idea of unit tests where I want to get rid of any external dependencies: If for some reason an ArcObjects license is not available in the test environment, the tests will fail - but only because the license could not be initialized. I repeat, for the purpose of the tests I'm not interested in any real ArcObjects instances, therefore a license shouldn't be required.

But second, even if I do initialize an ArcObjects license, in the above method the Assert will fail because the IFields variable newFields created with new is in fact an instance of ESRI.ArcGIS.Geodatabase.FieldsClass instead of the fake I expected from Isolator!

I'm currently trying to sort this out with Typemock's support, but I would also like to know from Esri if they have any guidelines for writing unit tests for ArcObjects code, and if (and how) it is possible to fake any ArcObjects instances created in the production code. Is there an isolation framework that can do this? Can it be done with Typemock Isolator but requires some steps that I missed?

Are there any ArcObjects developers out there who came across the same issue and found a solution?

Best regards,
Fridjof
0 Kudos
2 Replies
JasonPike
Occasional Contributor
Hi all,

While diving into unit testing and isolation frameworks, I wanted to try Typemock Isolator to write unit tests for some legacy ArcObjects code. One of the main characteristics of good unit tests is that they test logical units in isolation, i.e., replacing dependencies with fakes. Typemock Isolator seems to have some advanced capabilities for this purpose.

In the production code, there are numerous occurrences of ArcObjects classes being instantiated, such as:
private void doSomething()
{
  ...
  IFields fields = new FieldsClass();
  this.doSomethingElse(fields);
  ...
}

What I basically want to do is to write tests in which I can test the logic of the production code units without accessing any real ArcObjects instances, and also without having to make any changes to the production code before writing tests.

However, there are several issues. First of all, even if I want to fake an ArcObjects class in Typemock Isolator, I need an ArcObjects license. If no license was initialized, the first line of the following method will fail with a COMException.
[Test]
public void SwapAllInstances_OfFieldsClass_SwapsFieldsClassCreatedWithNew()
{
    // Arrange
    FieldsClass fields = Isolate.Fake.Instance<FieldsClass>();
    Isolate.Swap.AllInstances<FieldsClass>().With(fields);

    // Act
    IFields newFields = new FieldsClass();

    // Assert
    Assert.That(newFields, Is.Not.InstanceOf<FieldsClass>());
}

This is actually against the idea of unit tests where I want to get rid of any external dependencies: If for some reason an ArcObjects license is not available in the test environment, the tests will fail - but only because the license could not be initialized. I repeat, for the purpose of the tests I'm not interested in any real ArcObjects instances, therefore a license shouldn't be required.

But second, even if I do initialize an ArcObjects license, in the above method the Assert will fail because the IFields variable newFields created with new is in fact an instance of ESRI.ArcGIS.Geodatabase.FieldsClass instead of the fake I expected from Isolator!

I'm currently trying to sort this out with Typemock's support, but I would also like to know from Esri if they have any guidelines for writing unit tests for ArcObjects code, and if (and how) it is possible to fake any ArcObjects instances created in the production code. Is there an isolation framework that can do this? Can it be done with Typemock Isolator but requires some steps that I missed?

Are there any ArcObjects developers out there who came across the same issue and found a solution?

Best regards,
Fridjof


Fridjof,

I think that the problem you're encountering is related to the way the mocking frameworks achieve their capabilities. Mocking frameworks create proxies for the objects that they are mocking, allowing you to intercept calls being made to instances of that type. You're essentially injecting a layer in between you and the implementation of that type.

If you are mocking an interface, there is no default implementation, so the proxy won't do anything if you haven't told it to. In your case, you're trying to mock a concrete class and not an interface, which introduces some complications. For concrete classes, only the virtual methods can be proxied because the proxy is achieved by sub-classing off the type being mocked. My understanding is that the proxy has to create an instance of the FieldsClass COM object so that it can call the non-virtual methods on that instance. It is this creation that is requiring licensing and probably the source of your other problems. I don't think you would have this problem if you were trying to mock an interface like IFields or IFieldsEdit.

I've been using Moq to mock ArcObjects types for unit testing older code with a lot of success. However, I am having to change the code in some cases to facilitate the testing (inversion of control, dependency injection, and programming to interfaces instead of concretes.) I'm afraid you can expect to have to refactor to a different design in order to test code that wasn't written with unit testing in mind.

You can read more here:

Discussion of Castle's DynamicProxy, which is used by Moq and RhinoMocks (possibly used by Typemock ...

Proxy Design Pattern

I hope this helps you. Please post what you hear from Typemock's support team.
0 Kudos
FridjofSchmidt
Occasional Contributor
Fridjof,

I think that the problem you're encountering is related to the way the mocking frameworks achieve their capabilities. Mocking frameworks create proxies for the objects that they are mocking, allowing you to intercept calls being made to instances of that type. You're essentially injecting a layer in between you and the implementation of that type.

If you are mocking an interface, there is no default implementation, so the proxy won't do anything if you haven't told it to. In your case, you're trying to mock a concrete class and not an interface, which introduces some complications. For concrete classes, only the virtual methods can be proxied because the proxy is achieved by sub-classing off the type being mocked. My understanding is that the proxy has to create an instance of the FieldsClass COM object so that it can call the non-virtual methods on that instance. It is this creation that is requiring licensing and probably the source of your other problems. I don't think you would have this problem if you were trying to mock an interface like IFields or IFieldsEdit.


Jason,

I agree that faking interfaces is not a problem, and therefore with some refactoring of the code towards testability I would be able to achieve reasonable results for unit tests with either of the frameworks you mentioned. The reason why I initiated this post was that I had come across a comparison of isolation frameworks by Roy Osherove ("The Art of Unit Testing") where he states that there are unconstrained frameworks that can fake anything, one of them being Typemock Isolator.

Well, it turns out that Typemock Isolator currently (as of version 7.1.6) does not support faking of COM objects. This is what I was told by Typemock Support. It does, however, support faking of class instances, static classes, even private and static methods, etc., but only in the realm of .NET managed code as it seems. Faking COM objects is on their backlog but not yet implemented. Faking COM interfaces works, therefore dependency injection will be my friend.

This means for me that in fact I will have to refactor the code a bit in order to implement unit tests. I actually wanted to avoid refactoring before writing the tests, and if it were a project without any COM components it would be possible to follow this approach, but not with ArcObjects code involved where COM classes are created with new.

Thanks for your advice anyway. I really hope that Esri will publish a "Best Practices for Unit Testing with ArcObjects" document some day because I think (and I hope) that we're not the only ones who want to write testable code, and also I'm wondering how Esri developers themselves do this. Unfortunately, the developer help does not talk about it anywhere.

Best regards,
Fridjof
0 Kudos