Autofixture: AutoMoq конкретная зависимость

Созданный на 1 окт. 2020  ·  15Комментарии  ·  Источник: AutoFixture/AutoFixture

Привет,

Я пытаюсь использовать Moq конкретной зависимости.

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ServiceDiscoveryUsingIdentityServer sut
)
{...}

Это вызывает ошибку, когда AutoFixture пытается создать реальный экземпляр IdentityServerOptions для внедрения в мой sut вместо использования замороженного макета.
Если я удалю шов и вручную создаю переход в identityServerOptions.Object тогда все будет работать, поэтому моя рабочая теория заключается в том, что autofixture / automoq не улавливает, что замороженный moq может удовлетворить зависимость, и поэтому пытается создать новый пример.

Я также попробовал Frozen(Matching.DirectBaseType) поскольку прокси, сгенерированный moq, наследует запрошенное значение, но все равно не повезло.

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

Спасибо
Юан

question

Самый полезный комментарий

Наконец-то появилась возможность протестировать его, и он отлично работает.
Я сделал небольшое улучшение (я знаю, что он все еще недостаточно надежен для всех применений), чтобы вы все еще могли запрашивать конкретный объект вместо оболочки Mock.

public void Customize(IFixture fixture)
{
    Type? mockedType = (typeof(IMock<object>).IsAssignableFrom(ParameterInfo.ParameterType))
        ? ParameterInfo.ParameterType.GetGenericArguments()[0]
        : ParameterInfo.ParameterType;
    fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
}

Позволяет

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] DependencyClass dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock, sut.Dependency);
}

Все 15 Комментарий

Просто заморозьте IdentityServerOptions и используйте Mock.Get .

Проверьте эту страницу буклета, который я написал для своих разработчиков: https://docs.educationsmediagroup.com/unit-testing-csharp/autofixture/combining-autofixture-with-nunit-and-moq

К сожалению, это не работает, поскольку IdentityServerOptions не является абстрактным классом или интерфейсом, и поэтому AutoMoqCustomization не генерирует для него Moq.

Атрибут для полноты:

public AutoMoqDataAttribute()
    : base(() => new Fixture().Customize(new AutoMoqCustomization()))
{ }

Обратите внимание, что для интерфейсов и абстрактного класса не имеет значения, заморозите ли вы фиктивную оболочку или интерфейс, а затем либо получите макет, либо используете .Object в своем тестовом примере.

Я считаю, что совершенно правильно, что конкретный класс в параметрах должен давать вам реальную копию, иначе ваш sut не будет работать, но если вы укажете зависимость с помощью оболочки Mock, чтобы заставить макет, тогда Mock<IInterface> работает, как и следовало ожидать, а Mock<Concrete> - нет.

Тогда я не понимаю твоей проблемы. Зачем вообще нужно использовать Moq?

IdentityServerOptions не является моим классом, и его нельзя легко создать из модульного теста, поэтому мне нужно использовать имитацию, чтобы передать его в мой sut . Все насмешки отлично работают, но похоже, что AutoFixture / AutoMoq не учитывает тот факт, что Mockможет использоваться вместо зависимости IdentityServerOptions .

Текущее решение:

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] ILogger<ServiceDiscoveryUsingIdentityServer> logger,
    [Frozen] IAuditor<ServiceDiscoveryUsingIdentityServer> auditor,
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ... the other dependencies.
)
{
   //Arrange
   var sut= new ServiceDiscoveryUsingIdentityServer(logger, auditor, identityServerOptions, identityService, authorizationService, grpcService);
}

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

@ Юан-МакВи извини за поздний ответ.
Я надеюсь пролить свет на проблему, с которой вы столкнулись, и, возможно, найти обходной путь, который может вам помочь.

Хотя меня не было при разработке AutoMoq, я считаю, что такое поведение было задумано.

Как вы, вероятно, знаете, AutoMoq может вводить макеты двумя способами: как макет, так и как макет типа.
Текущая реализация по умолчанию имитирует любой интерфейс или абстрактный тип, поскольку никакие другие настройки уже не перехватили запрос для того же типа. Вот почему автоматическая имитация выполняется для запросов, которые прошли через весь конвейер разрешения, прямо перед выдачей исключения, потому что запрос не может быть удовлетворен.

Если бы мы переместили настройки авто-имитации ближе к началу конвейера, то большинство типов было бы разрешено как фиктивные. Это нежелательно, потому что тогда зависимости типов будут null или макет будет выдан при создании, потому что некоторые защитные предложения предотвращают создание объекта в недопустимом состоянии.

Теперь может показаться логичным, что макеты [Frozen] будут правильно разрешены, но из-за внутренней конструкции мы не можем сказать, какие типы замораживаются / вводятся, потому что условия, при которых всегда будет возвращаться один и тот же экземпляр, слишком сложны.
Контекстный запрос для имитации фиктивного типа почти всегда будет давать значение, поскольку имитировать можно практически любой тип, и мы не узнаем, удовлетворен ли запрос замороженным значением.

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

public class CustomCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<IdentityServerOptions>(
            x => x.FromFactory<IFixture>(
                f => f.Create<Mock<IdentityServerOptions>>().Object));
    }
}

Поскольку вы уже заморозили макет в своем тестовом параметре, когда вы запросили его в своем SUT, вы получите тот же экземпляр.

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen] Mock<IdentityServerOptions> identityServerOptions,
    ServiceDiscoveryUsingIdentityServer sut)

Я ненадолго оставлю этот вопрос открытым, на случай, если у вас возникнут дополнительные вопросы по этой теме.

Спасибо за ответ.

Сработало бы, если бы я создал собственный MockAttribute : CustomizeAttribute чтобы я мог делать что-то вроде:

[Theory, AutoMoqData]
public async Task Can_register_a_single_service_successfully(
    [Frozen, Mock(typeof(IdentityServerOptions))] IdentityServerOptions identityServerOptions,
    [Frozen] ILogger<ServiceDiscoveryUsingIdentityServer> logger,
    [Frozen] IAuditor<ServiceDiscoveryUsingIdentityServer> auditor,
    ServiceDiscoveryUsingIdentityServer sut)

Это было бы предпочтительнее, поскольку тогда стандартный атрибут AutoMoqData работает для генерации других параметров, поэтому я могу контролировать конкретные макеты конкретных объектов для каждого теста, вместо того, чтобы иметь множество атрибутов стиля AutoMoqWithMockXYZ для других сценариев.

Возможно ли это сработает (у меня еще не было возможности протестировать), или это не будет правильно сочетаться с Frozen и базовым AutoMoqData ?

public AutoMoqDataAttribute()
    : base(() => new Fixture().Customize(new AutoMoqCustomization()))
{ }

Спасибо

@ Юан-МакВи Я полагаю, ты действительно мог бы это сделать. Вот наивная реализация настройки, которая может обеспечить насмешку для конкретного параметра

public class MockCustomization : ICustomization
{
    public MockCustomization(ParameterInfo parameterInfo)
    {
        ParameterInfo = parameterInfo;
    }

    public ParameterInfo ParameterInfo { get; }

    public void Customize(IFixture fixture)
    {
        var mockedType = ParameterInfo.ParameterType.GetGenericArguments()[0];
        fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
    }
}

Затем вы можете определить атрибут, который его использует

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
public class MockAttribute : Attribute, IParameterCustomizationSource
{
    public ICustomization GetCustomization(ParameterInfo parameter)
    {
        return new MockCustomization(parameter);
    }
}

и, наконец, тест будет выглядеть примерно так

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] Mock<DependencyClass> dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock.Object, sut.Dependency);
}

Закрытие вопроса.
Вопрос вроде бы получил ответ.

Выглядит отлично, спасибо.

@aivascu, как вы думаете, можно ли включить перечисленные выше типы в библиотеку AutoMoq?

Они кажутся достаточно универсальными и универсальными, чтобы заслужить эту честь.

@Kralizek да, было бы полезно иметь его в AutoMoq lib.
Я добавлю проблему в журнал с предложением по этой функции.

Наконец-то появилась возможность протестировать его, и он отлично работает.
Я сделал небольшое улучшение (я знаю, что он все еще недостаточно надежен для всех применений), чтобы вы все еще могли запрашивать конкретный объект вместо оболочки Mock.

public void Customize(IFixture fixture)
{
    Type? mockedType = (typeof(IMock<object>).IsAssignableFrom(ParameterInfo.ParameterType))
        ? ParameterInfo.ParameterType.GetGenericArguments()[0]
        : ParameterInfo.ParameterType;
    fixture.Customizations.Add(new MockRelay(new ExactTypeSpecification(mockedType)));
}

Позволяет

[Theory]
[AutoData]
public void DependencyShouldBeSameAsMockedObject(
    [Frozen][Mock] DependencyClass dependencyMock,
    SystemUnderTest sut)
{
    Assert.Same(dependencyMock, sut.Dependency);
}

@Kralizek да, было бы полезно иметь его в AutoMoq lib.
Я добавлю проблему в журнал с предложением по этой функции.

Поскольку я недавно наткнулся на эту самую проблему, не возражаете ли вы, что я предложу PR для реализации MockAttribute как вы указали?

@aivascu

@Kralizek да, пожалуйста, сделайте, если вы это реализовали. AFAIR, когда я пытался реализовать, это было не так тривиально, как реализация сверху. Перенесем обсуждение в # 1269.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги