Autofac 整合測試 在 ConfigureContainer 之後進行 Mock 注入

lindexi發表於2021-05-29

在使用 Autofac 框架進行開發後,編寫整合測試時,需要用 Mock 的用於測試的模擬的型別去代替容器裡面已注入的實際型別,也就需要在 Autofac 完全收集完成之後,再次注入模擬的物件進行覆蓋原有業務程式碼註冊的正式物件。但 Autofac 預設沒有提供此機制,我閱讀了 Autofac 的原始碼之後,建立了一些輔助程式碼,實現了此功能。本文將告訴大家如何在整合測試裡面,在使用了 Autofac 的專案裡面,在所有收集完成之後,注入用於測試的 Mock 型別,和 Autofac 接入的原理

背景

為什麼選擇使用 Autofac 框架?原因是在此前的 WPF 專案裡面,有使用過的是 MEF 和 Autofac 兩個框架,而 MEF 的效能比較糟心。解決 MEF 效能問題的是 VS-MEF 框架。在後續開發的一個 ASP.NET Core 專案裡面,也就自然選用了 Autofac 框架

對比原生的 ASP.NET Core 自帶的 DI 框架,使用 Autofac 的優勢在於支援模組化的初始化,支援屬性注入

預設的 Autofac 可以通過 Autofac.Extensions.DependencyInjection 將 Autofac 和 dotnet 通用依賴注入框架合入在一起,但在 Autofac 裡面的定製要求是在 Startup 的 ConfigureContainer 函式裡面進行依賴注入,也就是在預設的 ASP.NET Core 裡面沒有提供更靠後的依賴注入方法,可以在完成收集之後,再次注入測試所需要的型別,覆蓋業務程式碼裡面的實際物件

需求

假定在一個應用,如 ASP.NET Core 應用裡面,進行整合測試,想要在整合測試裡面,使用專案裡面的依賴注入關係,只是將部分型別替換為測試專案裡面的模擬的型別

而在應用裡面,實際的業務型別是在 Autofac 的 Module 進行注入的。在應用裡面的大概邏輯如下,在 Program 的 CreateHostBuilder 函式裡面通過 UseServiceProviderFactory 方法使用 Autofac 替換 原生 的框架

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                // 使用 auto fac 代替預設的 IOC 容器 
                .UseServiceProviderFactory(new AutofacServiceProviderFactory());

在 Startup 裡面新增 ConfigureContainer 方法,程式碼如下

        public void ConfigureContainer(ContainerBuilder builder)
        {
            
        }

在 ConfigureContainer 裡面注入具體的需要初始化的業務模組,例如 FooModule 模組。在具體模組裡面注入實際業務的型別,如 Foo 型別,程式碼如下

    public class Startup
    {
    	// 忽略程式碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new FooModule());
        }
    }

    class FooModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"06 FooModule");

            builder.RegisterType<Foo>().As<IFoo>();
        }
    }

    public class Foo : IFoo
    {
    }

    public interface IFoo
    {
    }

現在的需求是在整合測試裡面,將 IFoo 的實際型別從 Foo 替換為 TestFoo 型別

在整合測試專案裡面,可以使用如下程式碼獲取實際的專案的依賴注入收集

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替預設的 IOC 容器 
                    .UseServiceProviderFactory(new AutofacServiceProviderFactory());

                var host = hostBuilder.Build();

                var foo = host.Services.GetService<IFoo>();

以上的 foo 就是從收集的容器裡面獲取的 IFoo 物件,以上程式碼獲取到的是業務程式碼的 Foo 型別物件。假定需要讓容器裡面的 IFoo 的實際型別作為測試的 TestFoo 型別,就需要在實際專案的依賴注入收集完成之前,進行測試的注入

但實際上沒有在 Autofac 裡面找到任何的輔助方法可以用來實現此功能。如果是預設的應用框架,可以在 ConfigureWebHostDefaults 函式之後,通過 ConfigureServices 函式覆蓋在 Startup 的 ConfigureServices 函式注入的型別

實現方法

實現的方法是很簡單的,關於此實現為什麼能解決問題還請參閱下文的原理部分

整合測試專案不需要改動原有的業務專案即可完成測試,實現方法是在整合測試專案裡面新增 FakeAutofacServiceProviderFactory 用來替換 Autofac 的 AutofacServiceProviderFactory 型別,程式碼如下

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

可以看到本質的 FakeAutofacServiceProviderFactory 實現就是通過 AutofacServiceProviderFactory 的屬性實現,只是在 CreateServiceProvider 方法裡面加入了委託,可以方便在單元測試裡面進行注入自己的方法

在整合測試專案裡面的使用方法如下

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替預設的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            builder.RegisterModule<TestModule>();
                        }));

傳入的委託需要注入測試的初始化模組,也就是 TestModule 需要加入注入,通過上面程式碼,可以讓 TestModule 在依賴注入的最後進行初始化。在 TestModule 裡面加入實際的測試型別注入的程式碼

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

    class TestFoo : IFoo
    {
    }

通過上面方法就可以讓整合測試專案從容器裡面獲取 IFoo 的物件,拿到的是 TestFoo 型別,整合測試專案的程式碼如下

    [TestClass]
    public class FooTest
    {
        [ContractTestCase]
        public void Test()
        {
            "依賴注入的時機,可以在完成收集之後,覆蓋原有的型別".Test(() =>
            {
                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    // 使用 auto fac 代替預設的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            builder.RegisterModule<TestModule>();
                        }));

                var host = hostBuilder.Build();

                var foo = host.Services.GetService<IFoo>();
                Assert.IsInstanceOfType(foo, typeof(TestFoo));
            });
        }
    }

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

    class TestFoo : IFoo
    {
    }

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

以上整合測試使用了 CUnit 中文單元測試框架輔助,在上面程式碼裡面,可以看到整合測試裡面的容器拿到的 IFoo 物件就是 TestFoo 型別。通過這個方法就可以在業務程式碼執行過程,注入測試需要的型別

為什麼通過以上的程式碼即可實現此功能,為什麼需要自己實現一個 FakeAutofacServiceProviderFactory 型別,為什麼不能在 AutofacServiceProviderFactory.CreateServiceProvider 方法之前注入型別,而是需要再定義一個 TestModule 模組,在測試初始化模組進行初始化。更多細節請看下文

原理

回答以上問題,需要了解各個注入方法呼叫的順序,我在程式碼裡面通過控制檯輸出各個方法的順序。標記了順序的程式碼放在本文最後

以下是按照呼叫順序的方法程式碼

Startup 的 ConfigureServices 方法

    public class Startup
    {
        // 忽略程式碼

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine($"01 ConfigureServices");
        }
    }

在 Startup 的 ConfigureServices 是依賴注入中最開始呼叫的方法,這也是原生的框架自帶的方法

IHostBuilder 的 ConfigureServices 擴充套件方法

                var hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    .ConfigureServices(collection => Console.WriteLine($"02 ConfigureServices Delegate"))

在 IHostBuilder 的 ConfigureServices 擴充套件方法將會在 Startup 的 ConfigureServices 方法執行完成之後呼叫,因此如果只使用原生的依賴注入,可以在此方法進行覆蓋,也就是如果沒有使用 Autofac 框架,只使用原生的框架,可以在整合測試,在此方法注入測試的型別

Startup 的 ConfigureContainer 方法

    public class Startup
    {
        // 忽略程式碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
            Console.WriteLine($"03 ConfigureContainer");
            builder.RegisterModule(new FooModule());
        }
    }

可以看到 public void ConfigureContainer(ContainerBuilder builder) 方法的呼叫在 ConfigureServices 方法之後,在 Autofac 也通過此機制實現代替原生的依賴注入功能,也通過此方法從原生的注入獲取依賴

關於 Autofac 的實際邏輯,請參閱下文

FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法

在如上程式碼,我們編寫了 FakeAutofacServiceProviderFactory 用於替換 AutofacServiceProviderFactory 型別。在 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法將會在呼叫 ConfigureContainer 之後執行

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        public FakeAutofacServiceProviderFactory(
            ContainerBuildOptions containerBuildOptions = ContainerBuildOptions.None,
            Action<ContainerBuilder>? configurationActionOnBefore = null,
            Action<ContainerBuilder>? configurationActionOnAfter = null)
        {
            _configurationActionOnAfter = configurationActionOnAfter;
            AutofacServiceProviderFactory =
                new AutofacServiceProviderFactory(containerBuildOptions, configurationActionOnBefore);
        }

        private AutofacServiceProviderFactory AutofacServiceProviderFactory { get; }
        private readonly Action<ContainerBuilder>? _configurationActionOnAfter;

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            Console.WriteLine($"04 FakeAutofacServiceProviderFactory");
            _configurationActionOnAfter?.Invoke(containerBuilder);
            return AutofacServiceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

在以上的 CreateServiceProvider 方法將會在 Startup 的 ConfigureContainer 方法之後執行,如上面程式碼。按照上面程式碼,將會執行 _configurationActionOnAfter 委託

因此下一個執行的就是傳入的委託

                IHostBuilder? hostBuilder = Host.CreateDefaultBuilder()
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                        webBuilder.UseTestServer(); //關鍵是多了這一行建立TestServer
                    })
                    .ConfigureServices(collection => Console.WriteLine($"02 ConfigureServices Delegate"))
                    // 使用 auto fac 代替預設的 IOC 容器 
                    .UseServiceProviderFactory(new FakeAutofacServiceProviderFactory(configurationActionOnAfter:
                        builder =>
                        {
                            Console.WriteLine($"05 ConfigurationActionOnAfter");
                            builder.RegisterModule<TestModule>();
                        }));

也就是如上程式碼的 ConfigurationActionOnAfter 委託

但儘管 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 在 Startup 的 ConfigureContainer 方法之後執行,實際上很多開發者不會在 Startup 的 ConfigureContainer 方法完成註冊,而是在 ConfigureContainer 裡面初始化模組。如上面程式碼,在業務邏輯註冊的模組的初始化還沒被呼叫。只有在實際的 ContainerBuilder 呼叫 Build 方法,才會執行模組的 Load 方法

因此下一個呼叫就是業務邏輯註冊的模組

FooModule 的 Load 方法

按照 Autofac 的定義,在 ConfigureContainer 的 Build 方法裡面,才會執行模組的初始化,呼叫 Load 方法

    class FooModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"06 FooModule");

            builder.RegisterType<Foo>().As<IFoo>();
        }
    }

在 Autofac 裡面,將會按照模組註冊的順序,呼叫模組的 Load 方法,如上面程式碼,可以看到 TestModule 在最後被註冊,因此將會最後執行

TestModule 的 Load 方法

在上面程式碼 TestModule 是最後註冊到 Autofac 的模組,也就是 TestModule 將會最後被執行

    class TestModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            Console.WriteLine($"07 TestModule");

            builder.RegisterType<TestFoo>().As<IFoo>();
        }
    }

如上面程式碼,在 TestModule 注入的測試型別,將會替換業務程式碼的實際型別

Autofac 接入的方法

通過上面的方法呼叫順序,大家也可以瞭解為什麼整合測試的程式碼這樣寫就有效果。更深入的邏輯是 Autofac 的設計,為什麼可以讓 Autofac 框架可以接入到 ASP.NET Core 應用裡面,我在此前可一直都是在 WPF 框架使用的。這個問題其實很簡單,所有的 dotnet 專案,無論是 ASP.NET Core 還是 WPF 等,都是相同的 dotnet 邏輯,裝配方式都相同,只是頂層業務邏輯實現方法有所不同,因此只需要加一點適配邏輯就能通用

從上面專案安裝的 NuGet 包可以看到,安裝了 Autofac.Extensions.DependencyInjection 庫就是提供 Autofac 與 dotnet 通用依賴注入框架連結的功能,而 ASP.NET Core 原生的框架就是基於 dotnet 通用依賴注入框架,因此就能將 Autofac 接入到 ASP.NET Core 應用

在 UseServiceProviderFactory 方法裡面,將會執行 ASP.NET Core 框架的依賴注入容器相關方法,此方法注入的 IServiceProviderFactory 帶泛形的型別,將可以支援在 Startup 方法裡面新增 ConfigureContainer 方法,引數就是 IServiceProviderFactory 的泛形

如加入了 FakeAutofacServiceProviderFactory 型別,此型別繼承了 IServiceProviderFactory<ContainerBuilder> 介面,也就是 IServiceProviderFactory 的 泛形 是 ContainerBuilder 型別,因此可以在 Startup 的 ConfigureContainer 方法引數就是 ContainerBuilder 型別

    public class Startup
    {
        // 忽略程式碼

        public void ConfigureContainer(ContainerBuilder builder)
        {
          
        }
    }

而 ConfigureContainer 將會被 Microsoft.AspNetCore.Hosting.GenericWebHostBuilder 進行呼叫,在 GenericWebHostBuilder 的呼叫順序是先呼叫 ConfigureServices 再呼叫 對應的 ConfigureContainer 方法

在 Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider 方法就是實際建立容器的方法,這個方法裡面,將會先呼叫完成 ConfigureServices 的配置,然後再呼叫 ConfigureContainer 的配置,程式碼如下

    public class HostBuilder : IHostBuilder
    {
        private void CreateServiceProvider()
        {
           // 忽略程式碼
            var services = new ServiceCollection();

            foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
            {
                configureServicesAction(_hostBuilderContext, services);
            }

            object containerBuilder = _serviceProviderFactory.CreateBuilder(services);

            foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
            {
                containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
            }

            _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
        }
    }

此時的 _serviceProviderFactory 將會是注入的 FakeAutofacServiceProviderFactory 型別,將會呼叫對應的 CreateBuilder 方法,也就是如下程式碼將會呼叫

    class FakeAutofacServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
    {
        // 忽略程式碼
        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            return AutofacServiceProviderFactory.CreateBuilder(services);
        }
    }

在 HostBuilder 的 _configureContainerActions 委託呼叫 ConfigureContainer 的邏輯,實際就是 Startup 型別裡面定義的 ConfigureContainer 方法

因此就是先呼叫 Startup 型別和 IHostBuilder 的 ConfigureServices 方法,然後再呼叫 ConfigureContainer 方法

在 Autofac 的 AutofacServiceProviderFactory 在 CreateBuilder 方法就可以拿到了原生註冊的所有型別,因為在呼叫 CreateBuilder 之前已經完成了所有的原生邏輯

在 AutofacServiceProviderFactory 的 CreateBuilder 方法將會先建立 ContainerBuilder 物件,然後呼叫 Populate 方法,從原生的 IServiceCollection 獲取註冊的型別,重新放到 ContainerBuilder 容器

        public ContainerBuilder CreateBuilder(IServiceCollection services)
        {
            var builder = new ContainerBuilder();

            builder.Populate(services);

            _configurationAction(builder);

            return builder;
        }

上面程式碼的 ContainerBuilder 是 Autofac 框架的,而 Populate 是擴充套件方法,和 AutofacServiceProviderFactory 都是在 Autofac.Extensions.DependencyInjection 庫提供的,通過此擴充套件方法和 AutofacServiceProviderFactory 即可實現 Autofac 和 dotnet 原生接入。在 Populate 方法從 dotnet 原生拿到註冊的型別,放入到 Autofac 的 ContainerBuilder 裡,這樣所有之前使用 dotnet 原生注入的型別就可以在 Autofac 拿到

        public static void Populate(
            this ContainerBuilder builder,
            IEnumerable<ServiceDescriptor> descriptors,
            object lifetimeScopeTagForSingletons = null)
        {
            if (descriptors == null)
            {
                throw new ArgumentNullException(nameof(descriptors));
            }

            builder.RegisterType<AutofacServiceProvider>().As<IServiceProvider>().ExternallyOwned();
            builder.RegisterType<AutofacServiceScopeFactory>().As<IServiceScopeFactory>();

            Register(builder, descriptors, lifetimeScopeTagForSingletons);
        }

以上的 IEnumerable<ServiceDescriptor> 就是 IServiceCollection 型別的物件,實際程式碼是 Register 裡面拿到註冊的型別,重新放入到 Autofac 裡

        private static void Register(
            ContainerBuilder builder,
            IEnumerable<ServiceDescriptor> descriptors,
            object lifetimeScopeTagForSingletons)
        {
            foreach (var descriptor in descriptors)
            {
                if (descriptor.ImplementationType != null)
                {
                    // Test if the an open generic type is being registered
                    var serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
                    if (serviceTypeInfo.IsGenericTypeDefinition)
                    {
                        builder
                            .RegisterGeneric(descriptor.ImplementationType)
                            .As(descriptor.ServiceType)
                            .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons);
                    }
                    else
                    {
                        builder
                            .RegisterType(descriptor.ImplementationType)
                            .As(descriptor.ServiceType)
                            .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons);
                    }
                }
                else if (descriptor.ImplementationFactory != null)
                {
                    var registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, (context, parameters) =>
                        {
                            var serviceProvider = context.Resolve<IServiceProvider>();
                            return descriptor.ImplementationFactory(serviceProvider);
                        })
                        .ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons)
                        .CreateRegistration();

                    builder.RegisterComponent(registration);
                }
                else
                {
                    builder
                        .RegisterInstance(descriptor.ImplementationInstance)
                        .As(descriptor.ServiceType)
                        .ConfigureLifecycle(descriptor.Lifetime, null);
                }
            }
        }

上面程式碼拿到的 ServiceDescriptor 就是在原生框架裡面的注入型別的定義,可以看到這些都重新放到 Autofac 的容器裡面

這就是為什麼 Autofac 能拿到在 ASP.NET Core 框架裡面其他框架注入的型別的程式碼

在 HostBuilder 的 CreateServiceProvider 方法最後就是呼叫 IServiceProviderFactory 的 CreateServiceProvider 方法返回實際的容器

也就是呼叫了 Autofac 的 CreateServiceProvider 方法,程式碼如下

        public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
        {
            if (containerBuilder == null) throw new ArgumentNullException(nameof(containerBuilder));

            var container = containerBuilder.Build(_containerBuildOptions);

            return new AutofacServiceProvider(container);
        }

可以看到本質就是呼叫了 ContainerBuilder 的 Build 方法,而在 Build 方法裡面,才會初始化 Autofac 的模組。因此在 FakeAutofacServiceProviderFactory 的 CreateServiceProvider 方法裡面新增的程式碼,是不會在具體業務模組的初始化模組呼叫之前被呼叫。但在 Autofac 裡面,模組的初始化順序是模組加入 Autofac 的順序,因此可以在 FakeAutofacServiceProviderFactory 裡面再加入測試的模組,測試的模組將會是最後加入的模組,也就是將會最後被執行

因此想要在接入 Autofac 框架覆蓋業務邏輯註冊的型別,就需要在 Autofac 裡面註冊一個測試使用的模組,要求這個模組最後註冊,然後在此模組裡面進行註冊型別,這樣就可以讓測試模組註冊的型別是最後註冊的,覆蓋原有的型別。而想要讓測試模組最後註冊,就需要自己實現一個繼承 IServiceProviderFactory<ContainerBuilder> 的型別,才能在 AutofacServiceProviderFactory 的 CreateServiceProvider 方法呼叫之前註冊模組

雖然我很喜歡使用 Autofac 框架,但是我覺得在接入 ASP.NET Core 時,沒有很好加入測試的機制,而讓開發者需要自己理解底層的邏輯才能進行註冊測試的型別

這裡也需要給 dotnet 的設計點贊,在一開始的 ASP.NET Core 選擇依賴注入框架時,選擇的是 dotnet 通用依賴注入框架,而 dotnet 通用依賴注入框架最底層的是使用最初的裝配器介面,在 C# 語言裡面介面的定義是最通用的,介面只約束而不定義。通過這一套傳承的定義,可以讓 10 多年前的 Autofac 框架依然可以跑在現代的應用裡面

這 10 多年也不是 Autofac 啥都不做,上面的說法只是為了說明 dotnet 的相容性特別強和最初的 dotnet 設計大佬的強大

本文的實現方法,雖然程式碼很少,但要理解 dotnet 的依賴注入和 ASP.NET Core 的依賴注入使用,和 Autofac 的接入方法。看起來就是 Autofac 的接入機制其實沒有考慮全,當然,也許是我的技術不夠,也許有更好的實現方法,還請大佬們教教我

程式碼

本文所有程式碼放在 githubgitee 歡迎小夥伴訪問

相關文章