unit test are annoying and time consuming. ist not fun at all, and the customer does not see any value in tests…..
i hear this all the time, and at some point in my tech career i also belived this. Then i wrote my first test an found a bug. At this point lightbulds started to glow and i realised that i woul not found the bug bymyself.
In the worst case the customer would have found the bug :C
As we all know writing tests makes our software more stable and maintainability will be increased.
Table of Contents
unit tests with the inventor api
AutoCad Inventor makes it very difficult to write testable code. At some point i thought that i am lucky. The api is completly build by interfaces.
For examle the Application and the documents
Interfaces can be mocked and the next steps will be very easy. I was wrong!!! The api uses COM to work with inventor. With COM you can methods which ‚theoreticly‘ retuns a different value every time you call them.
active document and the COM magic
i came along this example of COM magic:
suspicious cast there is no type in the solution which is inherited from both ‚_Document‘ and AssemblyDocument. During runtime this cast works. When i take a closer look to the interfaces i agree that the complier is right.
AssemblyDocument does not inherit anything
[DispId(50332027)] _Document ActiveEditDocument { [DispId(50332027), MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] [return: MarshalAs(UnmanagedType.Interface)] get; }
As you can see the ‚ActiveDocument‘ returns a UnmanagedType.Interface. And the type is different than the type we need during the runtime(AssemblyDocument). Because of this i is almost impossible to mock this.
I needed another strategy
If i cannot mock the interface i create a wrapper/abstraction around it. I created an own abstraction (IApplicationService) to get all the nessesary informations/objects i need.
public interface IApplicationService : ICommonApplicationService { DesignViewRepresentations GetRepresentations(); RepresentationsManager GetRepresentationsManager(); ModelAnnotations GetDocumentAnnotations(); double GetActiveViewScale(); AttributeSets DocumentAttributeSets { get; } }
public class PartApplicationService : CommonApplicationService, IApplicationService { private readonly Application application; public PartApplicationService([NotNull] Application application) : base(application) { this.application = application ?? throw new ArgumentNullException(nameof(application)); } [CanBeNull] public DesignViewRepresentations GetRepresentations() { return GetRepresentationsManager().DesignViewRepresentations; } public RepresentationsManager GetRepresentationsManager() { var part = GetPartDocument(); var representationsManager = part?.ComponentDefinition.RepresentationsManager; return representationsManager; } [CanBeNull] private PartDocument GetPartDocument() { var part = ((PartDocument)application.ActiveDocument); return part; }
The PartApplicationService is close coupled with the code var part = ((PartDocument)application.ActiveDocument);
writing the first unit-test
now the tricky part starts. As i mentioned inventor makes it not easy for you to mock and test you code.
In this example i will show you how i solved the problem with the document attributes (with the attributes you can store information in almost every object ). The structure looks like this
Somehow Autodesk choose to use the ‚Iterator Pattern‘ and because of this mocking is more difficult. I use Moq. The Interfaces looks almost like this.
public interface AttributeSets : IEnumerable { ObjectTypeEnum Type { get; } object Parent { get; } new IEnumerator GetEnumerator(); int Count { get; } AttributeSet this[ object Index] {get; } bool get_NameIsUsed( string AttributeSetName) AttributeSet Add( string AttributeSetName, bool CopyWithOwner = false) DataIO DataIO { get; } AttributeSet AddTransient( string AttributeSetName, bool CopyWithOwner = false); bool ParentAvailable( out object Parent, out object Context); } ... public interface IEnumerable { IEnumerator GetEnumerator(); }
We need to mock the complete Collection behaviour. I did this using container to provide my own lists.
public static Mock<AttributeSets> CreateAttributeSets(AttributeSetsContainer container) { var attSetsMock = new Mock<AttributeSets>(); attSetsMock.Setup(x => x.Count).Returns(() => { return container.AttributeSets.Count; }); attSetsMock.Setup(m => m[It.IsAny<int>()]).Returns<int>(i => container.AttributeSets.ElementAt(i)); attSetsMock.As<IEnumerable>().Setup(m => m.GetEnumerator()).Returns(() => container.AttributeSets.GetEnumerator()); attSetsMock.Setup(x => x.Add(It.IsAny<string>(), It.IsAny<bool>())) .Returns<string, bool>( (s, b) => { var c = new AttributeSetContainer() { Name = s }; var attributeSet = CreateAttributeSet(c, attSetsMock.Object); container.Containers.Add(c); container.AttributeSets.Add(attributeSet.Object); return attributeSet.Object; }); attSetsMock.Setup(x => x.get_NameIsUsed(It.IsAny<string>())) .Returns<string>(r => container.AttributeSets.Any(y => y.Name == r)); return attSetsMock; } ---------------------------------------------------------------- public class AttributeSetsContainer { public AttributeSetsContainer() { AttributeSets = new List<AttributeSet>(); Containers = new List<AttributeSetContainer>(); } public string Name { get; set; } public List<AttributeSet> AttributeSets { get; set; } public List<AttributeSetContainer> Containers { get; set; } }
[TestMethod] public void get_attributeset_attribute_does_not_exists_should_be_create() { var container = new AttributeSetsContainer(); var attsetsMock = MockFactory.CreateAttributeSets(container); var applicationServiceMock = new Mock<IApplicationService>(); applicationServiceMock.Setup(x => x.DocumentAttributeSets).Returns(attsetsMock.Object); var service = new AssemblyDocumentAttributeService(applicationServiceMock.Object); var attset = service.GetOrCreateActiveDocAttributeSet("bla"); attset.Should().NotBeNull(); attset.Name.Should().Be("bla"); }
and example of the used code can be found: https://github.com/HolzetheKid/InventorApiMock
Is there any chance of providing a sample project (solution) for this topic? I’d really like to understand this, as Inventor is quite difficult to test.
Hallo Jesse,
i created a small example. Hope it helps: https://github.com/HolzetheKid/InventorApiMockExample