与 Autofac 并行运行 xUnit 集成测试时无法加载类型 Castle.Proxies.IReadinessProxy

ibe*_*dev 3 c# castle-dynamicproxy xunit autofac eventstoredb

我遇到了一个问题,已经很多天无法解决。\n我将 xUnit 与给定的当时抽象一起使用,以使测试更具可读性。

\n

我正在使用 EventStore 的包装器并运行一些集成测试。它们都运行良好......除了一个在并行运行时失败(并且 xUnit 并行运行),但如果我按顺序运行它们,它们都会成功。

\n

我无法理解为什么这会成为一个问题,因为每个事实都应该运行构造函数(给定的)和要测试的功能(何时)。在每个事实中,我都会实例化一个 Autofac ContainerBuilder,构建容器并解析它IComponentContext,因此理论上每个测试都应该按预期隔离和幂等。

\n

这是我不断收到的异常:

\n
Autofac.Core.DependencyResolutionException : An exception was thrown while activating SalesOrder.EventStore.Infra.EventStore.EventStore -> SalesOrder.EventStore.Infra.EventStore.DomainEventsRetriever -> SalesOrder.EventStore.Infra.EventStore.Factories.DomainEventFactory -> \xce\xbb:SalesOrder.EventStore.Infra.EventStore.EventTypeResolver.\n---- System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.\nCould not load type \'Castle.Proxies.IReadinessProxy\' from assembly \'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\'.\n   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 136\n   at Autofac.Core.Resolving.InstanceLookup.Execute() in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 85\n   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\ResolveOperation.cs:line 130\n   at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\ResolveOperation.cs:line 83\n   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 1041\n   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 871\n   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 300\n   at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore.When() in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests\\EventStoreExtensionsTests\\ResolveTests.cs:line 53\n   at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore..ctor()\n----- Inner Stack Trace -----\n   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)\n   at System.Reflection.RuntimeModule.GetTypes()\n   at System.Reflection.Assembly.GetTypes()\n   at SalesOrder.EventStore.Infra.EventStore.Autofac.EventStoreExtensions.<>c.<RegisterResolvers>b__6_2(Assembly s) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac\\EventStoreExtensions.cs:line 174\n   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()\n   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\n   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)\n   at SalesOrder.EventStore.Infra.EventStore.Factories.EventTypeResolverFactory.Create(IEnumerable`1 existingTypes, IReadOnlyDictionary`2 domainEventSerializerDeserializers) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore\\Factories\\EventTypeResolverFactory.cs:line 16\n   at SalesOrder.EventStore.Infra.EventStore.Autofac.EventStoreExtensions.<>c__DisplayClass6_0.<RegisterResolvers>b__1(IComponentContext context) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac\\EventStoreExtensions.cs:line 180\n   at Autofac.Builder.RegistrationBuilder.<>c__DisplayClass0_0`1.<ForDelegate>b__0(IComponentContext c, IEnumerable`1 p) in C:\\projects\\autofac\\src\\Autofac\\Builder\\RegistrationBuilder.cs:line 62\n   at Autofac.Core.Activators.Delegate.DelegateActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Activators\\Delegate\\DelegateActivator.cs:line 71\n   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 118\n
Run Code Online (Sandbox Code Playgroud)\n

这是只有一个事实的测试,在与其他事实并行运行时会失败

\n
public class Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore\n    : Given_When_Then_Test\n{\n    private IEventStore _sut;\n    private IComponentContext _componentContext;\n\n    protected override void Given()\n    {\n        var builder = new ContainerBuilder();\n        builder\n            .RegisterEventStore(\n                ctx =>\n                {\n                    var eventStoreOptions =\n                        new EventStoreOptions\n                        {\n                            Url = EventStoreTestConstants.TestUrl,\n                            Port = EventStoreTestConstants.TestPort\n                        };\n                    return eventStoreOptions;\n                },\n                ctx =>\n                {\n                    var readinessOptions =\n                        new ReadinessOptions\n                        {\n                            Timeout = EventStoreTestConstants.TestTimeout\n                        };\n                    return readinessOptions;\n                });\n\n        var container = builder.Build();\n        _componentContext = container.Resolve<IComponentContext>();\n    }\n\n    protected override void When()\n    {\n        _sut = _componentContext.Resolve<IEventStore>();\n    }\n\n    [Fact]\n    public void Then_It_Should_Not_Be_Null()\n    {\n        _sut.Should().NotBeNull();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

知道这里可能发生什么吗?\n我已经查看了 Autofac 关于并发性的指南:https ://autofaccn.readthedocs.io/en/latest/advanced/concurrency.html但我认为我已经正确使用了组件上下文。

\n

更新 1:仅供参考,这是我使用的 GiveThenWhen 模板。但这里没什么特别的(我认为!)

\n
namespace ToolBelt.TestSupport\n{\n    public abstract class Given_When_Then_Test\n        : IDisposable\n    {\n        protected Given_When_Then_Test()\n        {\n            Setup();\n        }\n\n        private void Setup()\n        {\n            Given();\n            When();\n        }\n\n        protected abstract void Given();\n\n        protected abstract void When();\n\n        public void Dispose()\n        {\n            Cleanup();\n        }\n\n        protected virtual void Cleanup()\n        {\n        }\n    }\n    \n    public abstract class Given_WhenAsync_Then_Test\n        : IDisposable\n    {\n        protected Given_WhenAsync_Then_Test()\n        {\n            Task.Run(async () => { await SetupAsync(); }).GetAwaiter().GetResult();\n        }\n\n        private async Task SetupAsync()\n        {\n            Given();\n            await WhenAsync();\n        }\n\n        protected abstract void Given();\n\n        protected abstract Task WhenAsync();\n\n        public void Dispose()\n        {\n            Cleanup();\n        }\n\n        protected virtual void Cleanup()\n        {\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更新 2:这是我用于实现和所有测试的 IoCC Autofac 注册方法。有点复杂,因为我使用反射来保持 EventStore 包装器完全通用,但以防万一有人看到一些可能影响测试的奇怪内容。

\n
public static class EventStoreExtensions\n{\n    public static void RegisterEventStore(\n        this ContainerBuilder builder,\n        Func<IComponentContext, EventStoreOptions> optionsRetriever,\n        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever,\n        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever = null)\n    {\n        RegisterEventStoreConnection(builder, optionsRetriever);\n        RegisterFactories(builder);\n        RegisterEventStoreManager(builder);\n        RegisterConverters(builder);\n        RegisterResolvers(builder, customDomainEventMappersOptionsRetriever);\n        RegisterEventStoreServices(builder);\n        RegisterEventStoreReadinessCheck(builder, readinessOptionsRetriever);\n    }\n\n    private static void RegisterEventStoreReadinessCheck(\n        ContainerBuilder builder,\n        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever)\n    {\n        builder\n            .Register(context =>\n            {\n                var ctx = context.Resolve<IComponentContext>();\n                var readinessOptions = readinessOptionsRetriever.Invoke(ctx);\n\n                var timeout = readinessOptions.Timeout;\n                var eventStoreConnection = context.Resolve<IEventStoreConnection>();\n\n                var eventStoreReadiness =\n                    new EventStoreReadiness(\n                        eventStoreConnection,\n                        timeout);\n\n                return eventStoreReadiness;\n\n            })\n            .As<IEventStoreReadiness>()\n            .As<IReadiness>()\n            .SingleInstance();\n    }\n\n    private static void RegisterEventStoreConnection(\n        ContainerBuilder builder,\n        Func<IComponentContext, EventStoreOptions> optionsRetriever)\n    {\n        builder\n            .Register(context =>\n            {\n                var ctx = context.Resolve<IComponentContext>();\n                var eventStoreOptions = optionsRetriever.Invoke(ctx);\n\n                ConnectionSettings connectionSetting =\n                    ConnectionSettings\n                        .Create()\n                        .KeepReconnecting();\n\n                var urlString = eventStoreOptions.Url;\n                var port = eventStoreOptions.Port;\n                var ipAddress = IPAddress.Parse(urlString);\n                var tcpEndPoint = new IPEndPoint(ipAddress, port);\n\n                var eventStoreConnection = EventStoreConnection.Create(connectionSetting, tcpEndPoint);\n                return eventStoreConnection;\n            })\n            .As<IEventStoreConnection>()\n            .SingleInstance();\n    }\n\n    private static void RegisterFactories(\n        ContainerBuilder builder)\n    {\n        builder\n            .RegisterType<DomainEventFactory>()\n            .As<IDomainEventFactory>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<EventDataFactory>()\n            .As<IEventDataFactory>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<ExpectedVersionFactory>()\n            .As<IExpectedVersionFactory>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<IdFactory>()\n            .As<IIdFactory>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<StreamNameFactory>()\n            .As<IStreamNameFactory>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<RetrievedEventFactory>()\n            .As<IRetrievedEventFactory>()\n            .SingleInstance();\n    }\n\n    private static void RegisterEventStoreManager(ContainerBuilder builder)\n    {\n        builder\n            .RegisterType<EventStoreManager>()\n            .As<IEventStoreManager>()\n            .SingleInstance();\n    }\n\n    private static void RegisterConverters(ContainerBuilder builder)\n    {\n        builder\n            .Register(context =>\n            {\n                var utf8Encoding = new BytesConverter(Encoding.UTF8);\n                return utf8Encoding;\n            })\n            .As<IBytesConverter>()\n            .SingleInstance();\n\n        builder\n            .RegisterType<NewtonsoftConverter>()\n            .As<IJsonConverter>()\n            .SingleInstance();\n    }\n\n    private static void RegisterResolvers(\n        ContainerBuilder builder,\n        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever)\n    {\n        builder\n            .Register(context =>\n            {\n                var ctx = context.Resolve<IComponentContext>();\n                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);\n                var domainEventSerializerDeserializers =\n                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;\n                var mapperResolver = MapperResolverFactory.Create(domainEventSerializerDeserializers);\n                return mapperResolver;\n            })\n            .As<IMapperResolver>()\n            .SingleInstance();\n\n        builder\n            .Register(context =>\n            {\n                var ctx = context.Resolve<IComponentContext>();\n\n                var assembliesLoaded = AppDomain.CurrentDomain.GetAssemblies();\n                var domainEventTypes =\n                    assembliesLoaded\n                        .SelectMany(s => s.GetTypes())\n                        .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)\n                                    && x.IsClass);\n                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);\n                var domainEventSerializerDeserializers =\n                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;\n                var typeResolver =\n                    EventTypeResolverFactory.Create(\n                        domainEventTypes,\n                        domainEventSerializerDeserializers);\n                return typeResolver;\n            })\n            .As<IEventTypeResolver>()\n            .SingleInstance();\n    }\n\n    private static void RegisterEventStoreServices(ContainerBuilder builder)\n    {\n        builder\n            .RegisterType<EventStoreRepository>()\n            .As<IEventStoreRepository>();\n\n        builder\n            .RegisterType<DomainEventsPersister>()\n            .As<IDomainEventsPersister>();\n\n        builder\n            .RegisterType<DomainEventsRetriever>()\n            .As<IDomainEventsRetriever>();\n\n        builder\n            .RegisterType<EventStore>()\n            .As<IEventStore>();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更新 3:这是整个存储库,以防有人感到无聊并想要尝试重现它。它是一个通用事件存储包装器,用于使用官方 C# 驱动程序为 Greg Young 的事件存储产品实现的事件源。

\n

https://gitlab.com/DiegoDrivenDesign/DiDrDe.EventStore

\n

有趣的是,这个问题似乎每隔一段时间就会消失一次。事实上,通常情况下,重新启动电脑后,所有测试都会正常通过。其他时候则不然,所以我怀疑这与我在运行时加载程序集并且出现问题有关:(

\n
\n

更新于 2021 年 9 月 9 日\n我所做的异步执行并不完全正确。有一种方法可以使用 xUnit 来拥抱异步执行,该方法可能在这种竞争条件中发挥了作用。given-when-then这是我所有测试继承的模式的更好实现。感谢IAsyncLifetime我能够异步(并等待)前提条件和操作本身。

\n
using System.Threading.Tasks;\nusing Xunit;\n\nnamespace Rubiko.TestSupport.XUnit\n{\n    public abstract class Given_When_Then_Test_Async\n        : IAsyncLifetime\n    {\n        public async Task InitializeAsync()\n        {\n            await Given();\n            await When();\n        }\n\n        public async Task DisposeAsync()\n        {\n            await Cleanup();\n        }\n\n        protected virtual Task Cleanup()\n        {\n            return Task.CompletedTask;\n        }\n\n        protected abstract Task Given();\n\n        protected abstract Task When();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Dan*_*ica 6

DynamicProxyGenAssembly2 是由使用 CastleProxy 的模拟系统构建的临时程序集。NSubstitute 和 Moq 也有一些类似的未解决问题,表明该问题是 Castle.Core 甚至 .Net Framework 中的竞争条件(有关更多详细信息,请参阅:重型多线程代理生成下的 TypeLoadException 或 BadImageFormatException )