Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VSTO Unit Testing Office AddIn in C# .NET via RequestComAddInAutomationService

I have worked and read through various StackOverflow questions and other tutorials and documentation over the past weeks (N.B. some of them below) to try and find a way of unit testing a VSTO AddIn.

Unfortunately, it always results in an E_NOINTERFACE exception in my tests.

The code I am using is below - one extract of the ThisAddin partial class overriding the RequestComAddinAutomationService, another describing the test utility interface, the test itself, as well as an additional assembly extract proving that the AddIn assembly and its internals are visible to the test.

My question is: why is this not working? I am pretty sure that this follows the generally accepted practices of VSTO testing.

If the below is not possible anymore, how should one go about testing VSTO? Is .NET remoting/IPC the only solution?

ThisAddin.cs

public partial class ThisAddin 
{
    #region Testing Utilities

    private AddinHelper _comAddinObject;

    protected override object RequestComAddInAutomationService()
    {
        // This is being called when I debug/run my project, but not when I run the tests
        return _comAddinObject ?? (_comAddinObject = new AddinHelper());
    }

    #endregion
}

#region Testing Utilities

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddinHelper
{
    Presentation GetPresentation();
    string GetString();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IAddinHelper))]
public class AddinHelper : StandardOleMarshalObject, IAddinHelper
{
    public Presentation GetPresentation()
    {
        return Globals.ThisAddin... [...];
    }

    public string GetString()
    {
        return "Hello World!";
    }
}

#endregion

AssemblyInfo.cs

[assembly: InternalsVisibleTo("MyProject.Tests")]

MyUnitTest.cs (has a reference to MyProject)

[TestClass]
public class BasicTest
{
    [TestMethod]
    public void TestInstantiates()
    {
        var application = new Application();
        var doc = application.Presentations.Open(@"C:\Presentation.pptx",
            MsoTriState.msoFalse, MsoTriState.msoFalse, MsoTriState.msoFalse);
        var comAddins = application.COMAddIns;
        var comAddin = comAddins.Item("MyProject"); // Returns okay
        var comObject = (IAddinHelper) comAddin.Object; // Exception occurs

        Assert.AreEqual(true, true); // Never reached

        doc.Close();
    }
}

Furthermore, the project's settings are set to "Register for COM interop" and Visual Studio runs elevated without errors - running it as non-admin results in the DLLs not being registered; therefore, I also know that the COM objects are registered.

Resulting Exception

An exception of type 'System.InvalidCastException' occurred in MyProject.Tests.dll but was not handled in user code Additional information: Unable to cast COM object of type 'System.__ComObject' to interface type 'MyProject.IAddinHelper'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{59977378-CC79-3B27-9093-82CD7A05CF74}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

StackOverflow

  • How to call VSTO class from other c# project
  • Unit Testing VSTO projects
  • VSTO Add-ins, COMAddIns and RequestComAddInAutomationService (Register for COM Interop doesn't change anything)
  • Why cannot I cast my COM object to the interface it implements in C#? (the issue is likely not STAThread related)
  • https://sqa.stackexchange.com/questions/2545/how-do-i-unit-test-word-addin-written-in-c

Microsoft

  • https://blogs.msdn.microsoft.com/varsha/2010/08/17/writing-automated-test-cases-for-vsto-application/ <-- Even with this supposedly working sample project, I am getting the exact same error.
  • https://msdn.microsoft.com/en-us/library/bb608621.aspx
  • General workflow background: https://msdn.microsoft.com/en-us/library/bb386298.aspx

I honestly don't know how one is supposed to test VSTO Add-Ins without this working.

like image 481
JDR Avatar asked Jul 15 '17 18:07

JDR


Video Answer


1 Answers

Solution

The Project with methods to be Tested has to use the PIA Microsoft.Office.Interop.PowerPoint via the .Net reference tab.

In the Unit Test Project you have to use the Microsoft Powerpoint 1X.0 Object Library via the COM reference tab - its an ActiveX.

enter image description here

The confusing thing is in Solution Explorer they are both called: Microsoft.Office.Interop.Powerpoint


When you specify the correct References you'll see this error on the line where the exception occurs:

Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

To solve that simply add a .Net reference to the Microsoft.CSharp.dll in the Unit Test project.


Next we will run into the error you're seeing.

Firstly add a unique GUID to the Interface & class (this will overcome the error):

[ComVisible(true)]
[Guid("B523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IAddinHelper
{
    string GetPresentation();
    string GetString();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("A523844E-1A41-4118-A0F0-FDFA7BCD77C9")]
[ComSourceInterfaces(typeof(IAddinHelper))]
public class AddinHelper : StandardOleMarshalObject, IAddinHelper

Secondly temporarily make the private AddinHelper _comAddinObject; public in Scope (you can do your [assembly: InternalsVisibleTo("MyProject.Tests")] later when its working).

Thirdly, check that Powerpoint has not disabled the COM add-in. This sometimes happens silently, without the Office App complaining.

enter image description here

Lastly, make sure Register for COM is ticked.

Viola:

enter image description here


Ref: I pulled my hair out working this out years ago helping this fellow SO'r: Moq & Interop Types: works in VS2012, fails in VS2010?

like image 58
Jeremy Thompson Avatar answered Sep 21 '22 11:09

Jeremy Thompson