Change injection in Autofac module with a mock (Moq) object

5.2k views Asked by At

I have a Autofac module as below

    public class ServiceInjector:Module
    {
        protected override void Load(ContainerBuilder builder)
            {
                // many registrations and type looking up here
                ...
                // One of the registration, say t which is found
                // in above looking, is a resource consuming type

                builder.RegisterType(t).As<ITimeConsume>();

                // ...
            }
    }

And this module is used in a ServiceClass:

public class ServiceClass
{
    static IContainer _ioc;
    public ServiceClass()
    {
        var builder = new ContainerBuilder();
        builder.RegisterModule<ServiceInjector>();

        _ioc = builder.Build();     
    }

    public void InvokeService()
    {
        using(var scope = _ioc.BeginLifetimeScope())
        {
            ITimeConsume obj = scope.Resolve<ITimeConsume>(...);

            var result = obj.DoTimeConsumingJob(...);

            // do something about result here ...
        }    
    }
}

My questions is: how do I test ServiceClass by mocking (Moq) ITimeConsume class ? Here I try to write a test below:

public void Test()
{
    Mock<ITimeConsume> moc = GetMockObj(...);

    // How can I inject moc.Object into ServiceInjector module,
    // so that ServiceClass can use this mock object ?
}

If this is not possible for the way, what's a better design for mocking the time consuming class which can also be injected?

**

Update:

** Thanks @dubs and @OldFox hints. I think the key is that the Autofac injector should be initialized externally instead of internal controlled. So I leverage 'On Fly' building capability of Autofac.ILifetimeScope and design ServiceClass constructor with a LifeTime scope parameter. With this design I can on-flying registering any service in the unit test as below example:

using(var scope = Ioc.BeginLifetimeScope(
    builder =>  builder.RegisterInstance(mockObject).As<ITimeConsume>())
2

There are 2 answers

0
Old Fox On

In the current design you cannot inject your mock object. The simplest solution with the least changes is to add an Internal Cto'r to ServiceClass:

internal ServiceClass(IContainer ioc)
{
    _ioc = ioc;     
}

Then use the attributte InternalsVisibleTo to enable the using of the C`tor in your test class.

In the arrange/setup/testInit phase initialize your class under test with the container which contains the mock object:

    [SetUp]
    public void TestInit()
    {
        Mock<ITimeConsume> moc = GetMockObj(...);
        builder.RegisterInstance(moc).As<ITimeConsume>();
        ...
        ...
        _target = new ServiceClass(builder.Build());
    }
0
Dan On

Personally I have multiple container instances. One for each endpoint.

Test project

public class AutofacLoader
{
    public static void Configure()
    {
        var builder = new ContainerBuilder();

        builder.RegisterModule<ServiceProject.ServiceInjector>();
        builder.RegisterModule<LocalTestProject.AutofacModule>();

        Container = builder.Build();
    }

    public static IContainer Container { get; set; }
}

The local test project autofac module is then free to override the service project module with specific registrations.

If more than one component exposes the same service, Autofac will use the last registered component as the default provider of that service: http://autofac.readthedocs.org/en/latest/register/registration.html#default-registrations

Test class

public void Test()
{
    AutofacLoader.Configure();

    var x = AutofacLoader.Container.Resolve<ITimeConsume>();
}