I'm trying to create unit tests for an Excel VSTO add-in I've created, but I've ran into an incredibly mysterious issue that feels well beyond my experience.
In this case, I have a presenter:
public class Presenter
{
private readonly Excel.Application Application;
public Presenter(Excel.Application Application)
{
this.Application = Application;
Application.WorkbookActivate += Application_WorkbookActivate;
}
private void Application_WorkbookActivate(Excel.Workbook Wb)
{
// logic to be tested
}
}
My unit test is to verify that when WorkbookActivate is called, it performs a specific action, e.g.:
[Test]
public void TestLogicWhenWorkbookActivates()
{
var mockApplication = new Mock<Excel.Application>();
presenter = new Presenter(mockApplication.Object);
// Act
mockApplication.Raise(a => a.WorkbookActivate += null, (Excel.Workbook)null);
// Assert
// ...
}
Now, when I run this test, it fails when the event is added (which occurs in the presenter's constructor), throwing the following:
System.MissingMethodException : Error: Missing method 'instance void [ExcelAddIns.TestControl] Microsoft.Office.Interop.Excel.AppEvents_Event::add_WorkbookActivate(class Microsoft.Office.Interop.Excel.AppEvents_WorkbookActivateEventHandler)' from class 'Castle.Proxies.ApplicationProxy'.
My understanding, based on this related Stack Overflow post was that what I am performing is a third party callback, and that failed prior to Moq 4.0. I am using Moq 4.2.1402.2112.
So here's the weird part: in that Stack Overflow answer, the Moq bug report is mentioned, which possesses a unit test to test this very concept:
[Test]
public void InteropExcelTest()
{
var mockAppExcel = new Mock<Excel.Application>();
bool isDelegateCalled = false;
mockAppExcel.Object.WorkbookActivate += delegate { isDelegateCalled = true; };
mockAppExcel.Raise(ae => ae.WorkbookActivate += null, (Excel.Workbook)null);
Assert.True(isDelegateCalled);
}
And this test, indeed, passes, which implies that my first test should be valid as it is properly handling the event. But what's even stranger is that the inclusion of this test into my unit test .cs file causes the previously failing test (TestLogicWhenWorkbookActivates) to pass!
These tests are completely independent. Why is the presence of the second causing the first to pass?
After some additional research and experimentation, I believe I have solved my problem. I'm not an expert on interop, so if I make any false conclusions here please feel free to edit or correct me.
I was working with two projects:
ExcelAddIns.TestControl
ExcelAddIns.TestControl.Tests
Both required VSTO references (including Microsoft.Office.Interop.Excel, among others). All VSTO references had Embed Interop Types set to true (which I believe is the default). Based on what I've read (see references below), this was the key to the problem, as it effectively separated the assemblies' embedded types, causing a clash when my Unit Test referenced the TestControl project.
Thus, my solution was to set Embed Interop Types to false for all VSTO assembly references in both projects. This caused the unit test to pass without the presence of the second test.
References:
Stack Overflow: Moq & Interop Types: works in VS2012, fails in VS2010?
Stack Overflow: What's the difference setting Embed Interop Types true and false in Visual Studio?
Daniel Cazzulino's Blog: Check your Embed Interop Types flag when doing Visual Studio extensibility work
Related
I am working on a application which needs to communicate via COM interface with multiple CAD applications (not in the same time). I want to have nice and reusable code, but I came across problems with type casting of COM objects when I made generic application handle getter method.
What I tried so far:
This is the attempt I would like the most if it worked.
public static TCadAppType CadApp<TCadAppType>()
{
dynamic cadApp = default(TCadAppType);
//Here under Dynamic View/Message there is already an error
// Message = "Element not found. (Exception from HRESULT: 0x8002802B (TYPE_E_ELEMENTNOTFOUND))"
// cadVersion.Value evaluates to "SldWorks.Application"
cadApp = (TCadAppType)Marshal.GetActiveObject(cadVersion.Value);
//Following 2 lines of code are for testing purposes only, i am testing with Solidworks API
AssemblyDoc Assembly;
//The exception is thrown when I try to access some method from the Solidworks API
Assembly = (AssemblyDoc)cadApp.OpenDoc6("some parametras...");
}
Attempt using Convert class
// Another attempt using Convert class
public static TCadAppType CadApp<TCadAppType>()
{
dynamic cadApp = default(TCadAppType);
// cadVersion.Value evaluates to "SldWorks.Application"
cadApp = Marshal.GetActiveObject(cadVersion.Value);
cadApp = Convert.ChangeType(cadApp, typeof(SldWorks.SldWorks));
// Exception is thrown with the following message:
// Message = "Object must implement IConvertible."
}
I really thought that I am on the right track, since there is an article on Microsoft Docs website explaining how dynamic can help you with com interopt: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/using-type-dynamic#com-interop
Any ideas how I can do this runtime casting a keep my code as reusable as possible?
My software setup:
Win 10
Project is targeted for .NET 4.7.2
First Tests are with Solidworks 2019
Turns out that the my coding attempt 1 was valid c# code indeed.
I tried it using with Autodesk Inventor, and it works.
So the only thing left for me is to conclude that this is some bug from Solidworks and their COM interfacing.
Thank you Optional Option for your interest in the topic.
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.
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.
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.
Lastly, make sure Register for COM is ticked.
Viola:
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?
I am using NUnit to test one functionality where I need to load XML file to object. The XML file is in location of the Console Application.
I have Following method where configuration will be read :
public string GetConfiguration(TempFlexProcessor processor)
{
var exePath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var configPath = Path.Combine(Path.GetFullPath(exePath), "configuration");
var configFile = string.Format(#"{0}.xml", processor.GetType().Name);
}
Now in my NUnit Test I have test method where I test GetConfiguration :
[Test]
public void TempFlexProcessorExecuteTest()
{
#region Given
#endregion
#region When
var tempFlexProcessor = new TempFlexProcessor();
var actual = tempFlexProcessor.GetConfiguration(tempFlexProcessor);
#endregion
Assert.AreEqual("path of the file", actual);
}
But System.Reflection.Assembly.GetEntryAssembly() is null, please help.
I used AppDomain.CurrentDomain.BaseDirectory instead of System.Reflection.Assembly.GetEntryAssembly().Location
I suspect the problem is that NUnit is running your tests in a different AppDomain, but without using ExecuteAssembly. From the documentation for Assembly.GetEntryAssembly:
Gets the process executable in the default application domain. In other application domains, this is the first executable that was executed by AppDomain.ExecuteAssembly.
It's not clear which assembly you really want to get - even if this did return something "appropriate" for NUnit, that's likely to be the NUnit executable, which would be well away from any configuration directories you happen to have.
Basically, I think that you should at least provide an alternative way of specifying the configuration directory - and you might want to reconsider whether using GetEntryAssembly is a good idea anyway. (Aside from anything else, it's slightly odd that you're calling GetConfiguration on a processor and passing in another processor... that may be suitable for your design, but it's at least somewhat unusual, given that in your test case you're passing in a reference to the same object.)
In my Visual Studio 2012 solution I have a C# project for unit testing C++/CLI code, e.g.
...
using System.IO;
using Stuff;
namespace MyCLIClassTest
{
[TestClass]
public class MyCLIClassTest
{
public MyCLIClassTest() {}
[ClassInitialize]
public static void Setup(TestContext testContext)
{
}
[TestMethod]
public void LibraryAccessTest()
{
...
}
}
}
Now, the C# tests all fail with a message like "Method MyCLIClassTest.MyCLIClassTest.ClassInitialize has wrong signature. The method must be static, public, does not return a value and should take a single parameter of type TestContext."
After removing the ClassInitializer I got "Unable to set TestContext property for the class MyCLIClassTest.MyCLIClassTest. Error: System.ArgumentException: Object of type 'Microsoft.VisualStudio.TestPlatform.MSTestFramework.TestContextImplementation' cannot be converted to type 'Microsoft.VisualStudio.TestTools.UnitTesting.TestContext'..
I used DLLs of older unit testing framework versions. This happened because the project migrated recently to VS2012.
So, in the solution explorer under the test project you find "References". Right click it and select "Add reference..." to open the Reference Manager. Search for "unittest" and check the Microsoft.VisualStudio.QualityTools.UnitTestFramework with version number 10.1.0.0. Un-check all other versions of this assembly. Close the manager by clicking OK.
An alternate answer copied from a duplicate question: Why is a ClassInitialize decorated method making all my tests fail?
The [ClassInitialize] decorated method should be static and take exactly one parameter of type TestContext:
[ClassInitialize]
public static void SetupAuth(TestContext context)
{
var x = 0;
}
I had the exact same issue and removing/adding references as suggested by TobiMcNamobi did not solve it for me, however removing the reference, right click the project and selecting "Add > Unit test..." and thereby getting the reference re-generated worked. Not sure what the difference was compared to doing it manually.
Setup has wrong signature. Parameter 1 should be of type Microsoft.VisualStudio.TestTools.UnitTesting.TestContext.
I was running a Load Test Project and had both v10.0.0.0 versions of the DLLs:
Microsoft.VisualStudio.QualityTools.LoadTestFramework.dll
Microsoft.VisualStudio.QualityTools.WebTestFramework.dll
Changing the version LoadTestFramework to version 10.1 didn't fix it.
I had to goto my Unit Test Project and delete the MSTest.Adapter references:
Microsoft.VisualStudio.TestPlatform.TestFramework.dll
Microsoft.VisualStudio.TestPlatform.Extensions.dll
Then in the Unit Test Project add a reference to the v10.1
Microsoft.VisualStudio.QualityTools.LoadTestFramework.dll
I'm currently trying to load and use the Gephi Toolkit from within a .Net 4 C# website.
I have a version of the toolkit jar file compiled against the IKVM virtual machine, which works as expected from a command line application using the following code:
var controller = (ProjectController)Lookup.getDefault().lookup(typeof(ProjectController));
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
The three instances are correctly instantiated in a form similar to org.gephi.project.impl.ProjectControllerImpl#8ddb93.
If however I run the exact same code, with the exact same using statements & references, the very first line loading the ProjectController instance returns null.
I have tried a couple of solutions
Firstly, I have tried ignoring the Lookup.getDefault().lookup(type) call, instead trying to create my own instances:
var controller = new ProjectControllerImpl();
controller.closeCurrentProject();
controller.newProject();
var project = controller.getCurrentProject();
var workspace = controller.getCurrentWorkspace();
This fails at the line controller.newProject();, I think because internally (using reflector) the same Lookup.getDefault().lookup(type) is used in a constructor, returns null and then throws an exception.
Secondly, from here: Lookup in Jython (and Gephi) I have tried to set the %CLASSPATH% to the location of both the toolkit JAR and DLL files.
Is there a reason why the Lookup.getDefault().lookup(type) would not work in a web environment? I'm not a Java developer, so I am a bit out of my depth with the Java side of this.
I would have thought it possible to create all of the instances myself, but haven't been able to find a way to do so.
I also cannot find a way of seeing why the ProjectController load returned null. No exception is thrown, and unless I'm being very dumb, there doesn't appear to be a method to see the result of the attempted load.
Update - Answer
Based on the answer from Jeroen Frijters, I resolved the issue like this:
public class Global : System.Web.HttpApplication
{
public Global()
{
var assembly = Assembly.LoadFrom(Path.Combine(root, "gephi-toolkit.dll"));
var acl = new AssemblyClassLoader(assembly);
java.lang.Thread.currentThread().setContextClassLoader(new MySystemClassLoader(acl));
}
}
internal class MySystemClassLoader : ClassLoader
{
public MySystemClassLoader(ClassLoader parent)
: base(new AppDomainAssemblyClassLoader(typeof(MySystemClassLoader).Assembly))
{ }
}
The code ikvm.runtime.Startup.addBootClassPathAssemby() didn't seem to work for me, but from the provided link, I was able to find a solution that seems to work in all instances.
This is a Java class loader issue. In a command line app your main executable functions as the system class loader and knows how to load assembly dependencies, but in a web process there is no main executable so that system class loader doesn't know how to load anything useful.
One of the solutions is to call ikvm.runtime.Startup.addBootClassPathAssemby() to add the relevant assemblies to the boot class loader.
For more on IKVM class loading issues see http://sourceforge.net/apps/mediawiki/ikvm/index.php?title=ClassLoader