AutoFixture AutoMoq - SetReturnsDefault () не работает с макетами, созданными для приборов - PullRequest
1 голос
/ 26 марта 2020

У меня есть издевательство. У этого макета есть два метода: MethodA () и MethodB () . Я хочу настроить оба метода для возврата false . Я создал различные версии кода, все они должны работать, но некоторые не работают:

Эти работы:

1.

var mock = fixture.Freeze<Mock<MyInterface>>();
mock
    .Setup(m => m.MethodA(It.IsAny<T>(), It.IsAny<T>()))
    .ReturnsAsync(false);
mock
    .Setup(m => m.MethodB(It.IsAny<T>(), It.IsAny<T>()))
    .ReturnsAsync(false);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

2.

var mock = new Mock<MyInterface>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

3.

var mock = new Mock<MyInterface>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock.Object);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - Both return false, works

Это не:

4.

var mock = fixture.Freeze<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

5.

var mock = fixture.Create<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

6.

var mock = fixture.Create<Mock<MyInterface>>();
mock.SetReturnsDefault(Task.FromResult(false));
fixture.Inject(mock.Object);

var sut = fixture.Create<MySut>();
sut.Do(); // Calls MethodA() and MethodB() - MethodA returns true, fails

Судя по результатам, виновником является метод Fixture.Create () *. По какой-то причине, если макет создан с использованием fixture.Create () вместо new , это не сохранит конфигурацию, которую я настроил с помощью SetReturnsDefault () , даже если макет заморожен (то есть Fixture.Inject () был вызван для него). Может кто-нибудь объяснить, почему?


Сноска:

* Fixture.Create () также вызывается внутренне при вызове Fixture.Freeze () - Freeze - это просто сокращение для вызова Fixture.Create () , за которым следует Fixture.Inject ()

Следовательно, эти два фрагмента эквивалентны:

var mock = fixture.Freeze<Mock<MyInterface>>();

-

var mock = fixture.Create<Mock<MyInterface>>();
fixture.Inject(mock);

1 Ответ

1 голос
/ 18 апреля 2020

Поскольку вы поделились, что используете AutoMoqCustomization, это объясняет поведение, которое вы видите, хотя это связано с тем, что некоторые внутренние компоненты в Moq и AutoFixture взаимодействуют так, как вы этого не ожидаете.

Для полноты картины приведу заглушенную версию класса и интерфейса, которые вы упомянули в своем вопросе:

public interface IMyInterface
{
    Task<bool> MethodA();

    Task<bool> MethodB();
}

public class Sut
{
    private readonly IMyInterface dep;

    public Sut(IMyInterface dep)
    {
        this.dep = dep;
    }

    public async Task<bool> Do()
    {
        var one = await dep.MethodA();
        var two = await dep.MethodB();

        return one || two;
    }
}

А вот тест, который мы можем использовать для иллюстрации взаимодействия двух вариантов поведения:

[Test]
public async Task FixtureNoConfigureMembers()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization() { ConfigureMembers = false });

    var mock = fixture.Freeze<Mock<IMyInterface>>();

    mock.SetReturnsDefault(Task.FromResult(false));

    var sut = fixture.Create<Sut>();
    var result = await sut.Do();
    Assert.False(result);
}

Две важные детали реализации, которые следует учитывать:

  • Что делает AutoFixture, когда вы указываете ConfigureMembers = true в настройке.
  • Как Moq определяет, когда использовать значение по умолчанию, установленное SetReturnsDefault

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

Moq's SetReturnsDefault довольно просто Поведение:

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

На стороне автофиксатора ...

AutoMoqCustomization делает так, что, когда запрашивается образец типа Mock<T>, AutoFixture сначала создает Mock, фактически вызывая new Mock<T>() (это немного более нюансировано, чем это, но это по существу так этот интерфейс.)

После создания макета, если ConfigureMembers истинно, Autofixture дополнительно перечислит все виртуальные методы типа mocked и сделает эквивалент

mock.Setup(m => m.MethodName(It.IsAny<T>... for all arguments... ))
    .Returns(fixture.Create<TReturn>())

Это переопределяет поведение по умолчанию AutoMoqCustomization, которое позволяет Moq обрабатывать выбор значения по умолчанию (обычно с помощью создания своих собственных насмешек).

Вы, вероятно, можете видеть, где это возглавляется Поскольку AutoFixture создала настройки для всех виртуальных методов, это полностью переопределяет поведение SetReturnsDefault, предоставляемое Moq.

Обратите внимание, что это поведение связано с тем, что Moq раньше не использовал метод SetReturnsDefault - поэтому AutoFixture не может использовать DefaultValueProvider для введения образцов, которые он создает. На AutoFixture есть PR, чтобы изменить поведение для использования свойства DefaultValueProvider, предоставленного Moq. Предположительно это восстановит способность SetReturnsDefault переопределять поведение для определенного типа. (Хотя я не изучал PR вообще)

...