Не удалось загрузить тип Castle.Proxies.IReadinessProxy при запуске интеграционных тестов xUnit параллельно с Autofac.

У меня возникла проблема, которую я не мог решить много дней. Я использую xUnit с абстракцией «данные-тогда-когда», чтобы сделать тесты более читабельными.

Я использую оболочку над EventStore и запускаю некоторые интеграционные тесты. Все они идут хорошо ... кроме тот, который терпит неудачу при параллельной работе (и xUnit работает параллельно), но если я запускаю их все последовательно, все они преуспевают.

Я не могу понять, почему это может быть проблемой, потому что каждый отдельный факт должен запускать конструктор (данный) и функциональность для тестирования (когда). И в каждом факте я создаю экземпляр Autofac ContainerBuilder, создаю контейнер и разрешаю его IComponentContext, поэтому теоретически каждый тест должен быть изолированным и идемпотентным, как и предполагалось.

Это исключение, которое я продолжаю получать:

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 -> λ:SalesOrder.EventStore.Infra.EventStore.EventTypeResolver.
---- System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.
Could not load type 'Castle.Proxies.IReadinessProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 136
   at Autofac.Core.Resolving.InstanceLookup.Execute() in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 85
   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
   at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\Core\Resolving\ResolveOperation.cs:line 83
   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 1041
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 871
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 300
   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
   at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore..ctor()
----- Inner Stack Trace -----
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.RuntimeModule.GetTypes()
   at System.Reflection.Assembly.GetTypes()
   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
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   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
   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
   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
   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
   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 118

Это тест только с одним фактом, который не проходит параллельно с другими.:

public class Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore
    : Given_When_Then_Test
{
    private IEventStore _sut;
    private IComponentContext _componentContext;

    protected override void Given()
    {
        var builder = new ContainerBuilder();
        builder
            .RegisterEventStore(
                ctx =>
                {
                    var eventStoreOptions =
                        new EventStoreOptions
                        {
                            Url = EventStoreTestConstants.TestUrl,
                            Port = EventStoreTestConstants.TestPort
                        };
                    return eventStoreOptions;
                },
                ctx =>
                {
                    var readinessOptions =
                        new ReadinessOptions
                        {
                            Timeout = EventStoreTestConstants.TestTimeout
                        };
                    return readinessOptions;
                });

        var container = builder.Build();
        _componentContext = container.Resolve<IComponentContext>();
    }

    protected override void When()
    {
        _sut = _componentContext.Resolve<IEventStore>();
    }

    [Fact]
    public void Then_It_Should_Not_Be_Null()
    {
        _sut.Should().NotBeNull();
    }
}

Любая подсказка, что может происходить здесь? Я ознакомился с рекомендациями Autofac по параллелизму: https://autofaccn.readthedocs.io/en/latest/advanced/concurrency.html, но я думаю, что уже правильно использую контекст компонента.

ОБНОВЛЕНИЕ 1: К вашему сведению, это шаблон GivenThenWhen, который я использую. Но ничего особенного здесь (я думаю!)

namespace ToolBelt.TestSupport
{
    public abstract class Given_When_Then_Test
        : IDisposable
    {
        protected Given_When_Then_Test()
        {
            Setup();
        }

        private void Setup()
        {
            Given();
            When();
        }

        protected abstract void Given();

        protected abstract void When();

        public void Dispose()
        {
            Cleanup();
        }

        protected virtual void Cleanup()
        {
        }
    }
    
    public abstract class Given_WhenAsync_Then_Test
        : IDisposable
    {
        protected Given_WhenAsync_Then_Test()
        {
            Task.Run(async () => { await SetupAsync(); }).GetAwaiter().GetResult();
        }

        private async Task SetupAsync()
        {
            Given();
            await WhenAsync();
        }

        protected abstract void Given();

        protected abstract Task WhenAsync();

        public void Dispose()
        {
            Cleanup();
        }

        protected virtual void Cleanup()
        {
        }
    }
}

ОБНОВЛЕНИЕ 2: И это метод регистрации IoCC Autofac, который я использую для своей реализации и всех тестов. Немного сложно, потому что я использую отражение, чтобы обертка EventStore была полностью универсальной, но на всякий случай, если кто-то увидит там что-то странное, что может повлиять на тесты.

public static class EventStoreExtensions
{
    public static void RegisterEventStore(
        this ContainerBuilder builder,
        Func<IComponentContext, EventStoreOptions> optionsRetriever,
        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever,
        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever = null)
    {
        RegisterEventStoreConnection(builder, optionsRetriever);
        RegisterFactories(builder);
        RegisterEventStoreManager(builder);
        RegisterConverters(builder);
        RegisterResolvers(builder, customDomainEventMappersOptionsRetriever);
        RegisterEventStoreServices(builder);
        RegisterEventStoreReadinessCheck(builder, readinessOptionsRetriever);
    }

    private static void RegisterEventStoreReadinessCheck(
        ContainerBuilder builder,
        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var readinessOptions = readinessOptionsRetriever.Invoke(ctx);

                var timeout = readinessOptions.Timeout;
                var eventStoreConnection = context.Resolve<IEventStoreConnection>();

                var eventStoreReadiness =
                    new EventStoreReadiness(
                        eventStoreConnection,
                        timeout);

                return eventStoreReadiness;

            })
            .As<IEventStoreReadiness>()
            .As<IReadiness>()
            .SingleInstance();
    }

    private static void RegisterEventStoreConnection(
        ContainerBuilder builder,
        Func<IComponentContext, EventStoreOptions> optionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var eventStoreOptions = optionsRetriever.Invoke(ctx);

                ConnectionSettings connectionSetting =
                    ConnectionSettings
                        .Create()
                        .KeepReconnecting();

                var urlString = eventStoreOptions.Url;
                var port = eventStoreOptions.Port;
                var ipAddress = IPAddress.Parse(urlString);
                var tcpEndPoint = new IPEndPoint(ipAddress, port);

                var eventStoreConnection = EventStoreConnection.Create(connectionSetting, tcpEndPoint);
                return eventStoreConnection;
            })
            .As<IEventStoreConnection>()
            .SingleInstance();
    }

    private static void RegisterFactories(
        ContainerBuilder builder)
    {
        builder
            .RegisterType<DomainEventFactory>()
            .As<IDomainEventFactory>()
            .SingleInstance();

        builder
            .RegisterType<EventDataFactory>()
            .As<IEventDataFactory>()
            .SingleInstance();

        builder
            .RegisterType<ExpectedVersionFactory>()
            .As<IExpectedVersionFactory>()
            .SingleInstance();

        builder
            .RegisterType<IdFactory>()
            .As<IIdFactory>()
            .SingleInstance();

        builder
            .RegisterType<StreamNameFactory>()
            .As<IStreamNameFactory>()
            .SingleInstance();

        builder
            .RegisterType<RetrievedEventFactory>()
            .As<IRetrievedEventFactory>()
            .SingleInstance();
    }

    private static void RegisterEventStoreManager(ContainerBuilder builder)
    {
        builder
            .RegisterType<EventStoreManager>()
            .As<IEventStoreManager>()
            .SingleInstance();
    }

    private static void RegisterConverters(ContainerBuilder builder)
    {
        builder
            .Register(context =>
            {
                var utf8Encoding = new BytesConverter(Encoding.UTF8);
                return utf8Encoding;
            })
            .As<IBytesConverter>()
            .SingleInstance();

        builder
            .RegisterType<NewtonsoftConverter>()
            .As<IJsonConverter>()
            .SingleInstance();
    }

    private static void RegisterResolvers(
        ContainerBuilder builder,
        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);
                var domainEventSerializerDeserializers =
                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;
                var mapperResolver = MapperResolverFactory.Create(domainEventSerializerDeserializers);
                return mapperResolver;
            })
            .As<IMapperResolver>()
            .SingleInstance();

        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();

                var assembliesLoaded = AppDomain.CurrentDomain.GetAssemblies();
                var domainEventTypes =
                    assembliesLoaded
                        .SelectMany(s => s.GetTypes())
                        .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                    && x.IsClass);
                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);
                var domainEventSerializerDeserializers =
                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;
                var typeResolver =
                    EventTypeResolverFactory.Create(
                        domainEventTypes,
                        domainEventSerializerDeserializers);
                return typeResolver;
            })
            .As<IEventTypeResolver>()
            .SingleInstance();
    }

    private static void RegisterEventStoreServices(ContainerBuilder builder)
    {
        builder
            .RegisterType<EventStoreRepository>()
            .As<IEventStoreRepository>();

        builder
            .RegisterType<DomainEventsPersister>()
            .As<IDomainEventsPersister>();

        builder
            .RegisterType<DomainEventsRetriever>()
            .As<IDomainEventsRetriever>();

        builder
            .RegisterType<EventStore>()
            .As<IEventStore>();
    }
}

ОБНОВЛЕНИЕ 3: Это весь репозиторий на тот случай, если кому-то надоест и он захочет попробовать воспроизвести. Это универсальная оболочка хранилища событий, используемая для поиска источников событий, реализованная для продукта Грега Янга Event Store с использованием официального драйвера C#.

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

Как ни странно, эта проблема время от времени исчезает. На самом деле часто после перезагрузки ПК все тесты проходят нормально. В других случаях это не так, поэтому я подозреваю, что это как-то связано с тем фактом, что я загружаю сборки во время выполнения, и что-то выходит из строя :(


ОБНОВЛЕНИЕ 9 сентября 2021 г. Асинхронное выполнение, которое я делал, не совсем правильно. Существует способ использовать асинхронное выполнение с помощью xUnit, который, возможно, сыграл роль в этом состоянии гонки. Это лучшая реализация шаблона given-when-then, от которого наследуются все мои тесты. Благодаря IAsyncLifetime я могу асинхронно (и ждать) предварительные условия и само действие.

using System.Threading.Tasks;
using Xunit;

namespace Rubiko.TestSupport.XUnit
{
    public abstract class Given_When_Then_Test_Async
        : IAsyncLifetime
    {
        public async Task InitializeAsync()
        {
            await Given();
            await When();
        }

        public async Task DisposeAsync()
        {
            await Cleanup();
        }

        protected virtual Task Cleanup()
        {
            return Task.CompletedTask;
        }

        protected abstract Task Given();

        protected abstract Task When();
    }
}

Я пробовал вызывать RegisterEventStore с третьим параметром (необязательным) на тот случай, если необязательный параметр вызывал проблемы с отражением, но это тоже не помогло.

diegosasw 22.05.2019 15:54

ты тот самый парень? Просто для отслеживания решения :)

Legion 27.05.2019 16:41

Используете ли вы где-нибудь динамический прокси замка?

Cyril Durand 27.05.2019 16:45

Не тот парень. Динамический прокси-сервер должен использоваться внутри Moq или Autofac или... Я не использую его напрямую.

diegosasw 30.05.2019 15:47

Хотя Moq использует DynamicProxy, ни в вашем коде, ни в трассировке стека исключений нет никаких указаний на то, что здесь задействован Moq. Мне кажется более вероятным, что вы используете какое-то безымянное расширение Autofac, которое интегрируется с DynamicProxy.

stakx - no longer contributing 31.05.2019 11:31

Я отредактировал UPDATE 3 со всем репозиторием. Вы правы @stakx, я не думаю, что это имеет какое-то отношение к Moq. Я подозреваю, что это связано с Autofac и тем фактом, что я загружаю сборки. Иногда я не могу воспроизвести ошибку, поэтому очень расстраиваюсь, не зная, что ее вызывает.

diegosasw 31.05.2019 17:07

вы проверили мой ответ снизу?

Danut Radoaica 01.06.2019 11:34

да, это действительно может быть связано.

diegosasw 03.06.2019 10:20
3 метода стилизации элементов HTML
3 метода стилизации элементов HTML
Когда дело доходит до применения какого-либо стиля к нашему HTML, существует три подхода: встроенный, внутренний и внешний. Предпочтительным обычно...
Формы c голосовым вводом в React с помощью Speechly
Формы c голосовым вводом в React с помощью Speechly
Пытались ли вы когда-нибудь заполнить веб-форму в области электронной коммерции, которая требует много кликов и выбора? Вас попросят заполнить дату,...
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Стилизация и валидация html-формы без использования JavaScript (только HTML/CSS)
Будучи разработчиком веб-приложений, легко впасть в заблуждение, считая, что приложение без JavaScript не имеет права на жизнь. Нам становится удобно...
Flatpickr: простой модуль календаря для вашего приложения на React
Flatpickr: простой модуль календаря для вашего приложения на React
Если вы ищете пакет для быстрой интеграции календаря с выбором даты в ваше приложения, то библиотека Flatpickr отлично справится с этой задачей....
В чем разница между Promise и Observable?
В чем разница между Promise и Observable?
Разберитесь в этом вопросе, и вы значительно повысите уровень своей компетенции.
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Что такое cURL в PHP? Встроенные функции и пример GET запроса
Клиент для URL-адресов, cURL, позволяет взаимодействовать с множеством различных серверов по множеству различных протоколов с синтаксисом URL.
2
8
3 324
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Внутреннее исключение указывает, что тип не может быть загружен

Could not load type 'Castle.Proxies.IReadinessProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.

и это произошло из этой строки:

at System.Reflection.Assembly.GetTypes()
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
at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()

что соответствует этой строке в вашем коде

var domainEventTypes = assembliesLoaded
                          .SelectMany(s => s.GetTypes())
                          .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                      && x.IsClass);

Это означает, что одна из сборок содержит тип, который содержит ссылку на другую сборку, которая не загружена. Судя по названию типа с ошибкой, похоже, что это связано с автоматически сгенерированным типом замка.

Вы можете подписаться на статические события AppDomain.CurrentDomain.AssemblyResolve и AppDomain.CurrentDomain.TypeResolve, чтобы лучше понять, почему сборка не загружается, и, возможно, загрузить ее вручную. См. Assembly.GetTypes() — исключение ReflectionTypeLoadException для получения дополнительной информации.

В некоторых случаях исключение является «нормальным» и может быть проигнорировано с помощью такого кода:

public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
    // TODO: Argument validation
    try
    {
        return assembly.GetTypes();
    }
    catch (ReflectionTypeLoadException e)
    {
        return e.Types.Where(t => t != null);
    }
}

Код от Как предотвратить исключение ReflectionTypeLoadException при вызове Assembly.GetTypes()

И вы можете использовать это здесь:

var domainEventTypes = assembliesLoaded
                          .SelectMany(s => s.GetLoadableTypes())
                          .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                      && x.IsClass);

Я попробую в ближайшее время. То есть вы думаете, что это больше связано со скоростью загрузки сборок, а не с тем, как я регистрируюсь в автофаке? Как уже упоминалось, это происходит только при параллельном запуске всех тестов. Спасибо

diegosasw 27.05.2019 20:11

Ничего связанного с Autofac. Исключение составляет метод GetTypes. Я предполагаю, что у вас есть сборка в вашем домене приложения, в которой отсутствует сборка. Отсутствующий, похоже, связан с динамическим прокси Castle.Core. Вы можете найти больше информации, используя события AssemblyResolve и TypeResolveAppDomain.Current.

Cyril Durand 27.05.2019 20:35
Ответ принят как подходящий

DynamicProxyGenAssembly2 — это временная сборка, созданная фиктивными системами, использующими CastleProxy. Есть несколько похожих открытых проблем для NSubstitute и Moq, которые указывают на то, что проблема связана с состоянием гонки в Castle.Core или даже в .Net Framework (см. TypeLoadException или BadImageFormatException при интенсивной многопоточной генерации прокси для более подробной информации).

Если у вас есть COM-зависимости, убедитесь, что вы установили Встроить типы взаимодействия в Нет в своих проектах модульных тестов для этих COM-зависимостей. По умолчанию для этого свойства задано значение Да. Когда вы создаете макеты объектов в своих модульных тестах из этих COM-объектов, их не следует встраивать.

EmbedInteropTypes

Другие вопросы по теме