0

Working on a project in ASP.NET Web API 2 which has Autofac as my IoC container. This project is hosted on IIS and in my Autofac module I use the following method to scan for assemblies:

var asm = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();

Why?

https://docs.autofac.org/en/latest/register/scanning.html#iis-hosted-web-applications

But now we are making Unit Tests using NUnit, during my setup I register my module which uses this method. Now I receive the following exception when running my tests:

System.InvalidOperationException: 'This method cannot be called during the application's pre-start initialization phase.'

I understand why I have this exception but I don't have a clue how to make my code work in tests and for deployment environments.

Setup method of NUnit:

[TestFixture]
public abstract class ApplicationTestBase
{
    [SetUp]
    public override void Init()
    {
        var builder = new ContainerBuilder();

        // If the class requires auto mapper mapping, initialize them
        // We do this in order not to init them for every test => optimalisation!
        if (GetType().GetCustomAttributes<RequiresAutoMapperMappingsAttribute>(false) != null)
        {
            builder.RegisterModule<AutoMapperModule>();
        }

        this.Container = builder.Build();
    }
}

Do I need to create a new module specific for my Unit tests or is there another way for this?

AutoMapperTest

[RequiresAutoMapperMappings]
[TestFixture]
public class AutoMapperTests : ApplicationTestBase
{
    [Test]
    public void Assert_Valid_Mappings()
    {
        Mapper.AssertConfigurationIsValid();
    }
}

UPDATE

Like Cyril mentioned: Why do you need Ioc in your unit tests? I went searching and indeed you don't have to use the Ioc in your tests. So I ditched the Ioc and initialized my mapper configuration byy doing:

        Mapper.Initialize(configuration => 
        {
            var asm = AppDomain.CurrentDomain.GetAssemblies()
                        .Where(a => a.FullName.StartsWith("ProjectWebService."));

            configuration.AddProfiles(asm);
        });
Mivaweb
  • 5,580
  • 3
  • 27
  • 53
  • What are you testing ? why do you need IoC in your test ? See https://stackoverflow.com/questions/1465849/using-ioc-for-unit-testing – Cyril Durand Mar 08 '19 at 14:36
  • See my update, I have create a test to check if my AutoMapper configuration is valid. Therefore I need to register my AutoMapper profiles and configuration, which is done in the AutoMapper Module. – Mivaweb Mar 08 '19 at 19:27

1 Answers1

1

I would recommend separating the "how to load assemblies" logic from the "do assembly scanning and register modules logic."

Right now I'm guessing you have something like this all in one method.

public IContainer BuildContainer()
{
  var asm = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();
  var builder = new ContainerBuilder();
  builder.RegisterAssemblyTypes(asm);
  var container = builder.Build();
}

Not exactly that, but something similar - the loading of assemblies is inlined and directly used.

Separate that so you can swap that logic in for testing. For example, consider allowing a parameter to be optionally passed so you can override the logic in test.

public IContainer BuildContainer(Func<IEnumerable<Assembly>> assemblyLoader = null)
{
  IEnumerable<Assembly> asm = null;
  if (assemblyLoader != null)
  {
    asm = assemblyLoader();
  }
  else
  {
    asm = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();
  }

  var builder = new ContainerBuilder();
  builder.RegisterAssemblyTypes(asm);
  var container = builder.Build();
}

Your default logic will work the way you want, but then in testing you can swap in something else.

var container = BuildContainer(() => AppDomain.GetAssemblies());

There are lots of ways you can do that swap-in. It could be anything from a static property you can set somewhere to a virtual method you can override somewhere. The point is, by separating the assembly loading logic you can get the test-time behavior to work but still use the registration behavior you're after.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85