Search code examples
c#visual-studiounit-testingvisual-studio-2017appdomain

Unit Testing code that uses a separate AppDomain


I have code that uses an AppDomain to dynamically load plugins. I have a private internal class called PluginFinder to do the work. This all works in the production environment, but I am now trying to write MS Unit Tests for it, and I am getting an error saying the assembly could not be found.

To be clear, the main class creates the AppDomain and runs the PluginFinder in it to load classes it finds with the correct Interface. Once the classes are loaded, control returns to the default AppDomain. The PluginFinder is only used during loading.

I use the code below to create the environment:

            //  Create a domain to text for plugins
            AppDomain domain = AppDomain.CreateDomain("PluginLoader");

            //  Set PluginFinder to run in the new domain (assemblyName, typeName)
            (PluginFinder)domain.CreateInstanceAndUnwrap(
                typeof(PluginFinder).Assembly.FullName, typeof(PluginFinder).FullName);

PluginFinder uses a default constructor, and is a private internal class. The exception I get is below:

    ------ Exception ------
Error:  Could not load file or assembly 'MyClass, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
There was an error creating the Plugin Loader.
Could not load file or assembly 'MyClass, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

   at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
   at System.Activator.CreateInstance(String assemblyString, String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, Evidence securityInfo, StackCrawlMark& stackMark)
   at System.Activator.CreateInstance(String assemblyName, String typeName)
   at System.AppDomain.CreateInstance(String assemblyName, String typeName)
   at System.AppDomain.CreateInstanceAndUnwrap(String assemblyName, String typeName)
   at System.AppDomain.CreateInstanceAndUnwrap(String assemblyName, String typeName)
   at MyClass.loadPlugins() in C:\Code\...
---- End Exception ----

I have found this solution, which suggests that NUnit runs in multiple AppDomains, which makes testing of AppDomains impossible without plugins. I'm using MS Unit Test, but I suspect that this may be the case here as well.

**** RESOLVED ****

As was mentioned in the comment below, I needed to use the overload constructor. I modified my code (shown below), and it worked.

// Set up the AppDomainSetup
var setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;

// Set up the Evidence
var baseEvidence = AppDomain.CurrentDomain.Evidence;

//  Create a domain to text for plugins
AppDomain domain = AppDomain.CreateDomain("PluginLoader", baseEvidence, setup);

//  Set PluginFinder to run in the new domain (assemblyName, typeName)
PluginFinder finder = (PluginFinder)domain.CreateInstanceAndUnwrap(
    typeof(PluginFinder).Assembly.FullName, typeof(PluginFinder).FullName);

Solution

  • I'm not quite sure what NUnit is doing here, but the issue is probably that the new AppDomain's base directory is not what you expect. Consider using the parent domain's setup information or explicitly copying the base directly from the current domain when creating the new one:

    AppDomain.CreateDomain("PlugInLoader", null, AppDomain.CurrentDomain.SetupInformation);