AutoFixture AutoMoq не создает макеты для некоторых свойств

Я использую AutoFixture с AutoMoqCustomization и пытаюсь создать экземпляр класса, который содержит свойство только для чтения, таким образом:

public override ILog Logger { get; } = LogManager.GetLogger(typeof(MyService));

Идея в том, что я должен иметь возможность заморозить мой тест ILog test double, используя:

var log = fixture.Freeze<Mock<ILog>>;

и убедитесь, что он был вызван позже после вызова основного метода:

log.Verify(l => l.Warn, Times.Once);

Однако, когда я вызываю fixture.Create<MyService>, AutoFixture не заменяет свойство Logger имитацией ILog. Я также попытался удалить значение по умолчанию LogManager.GetLogger<etc>, и в этом случае значение ILog равно null.

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

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

public interface ILog
{
    bool IsDebugEnabled { get; }
    void Debug(object message);
    void Debug(object message, Exception exception);
    void DebugFormat(string format, params object[] args);
    void Error(object message);
    void Error(object message, Exception exception);
    void ErrorFormat(string format, params object[] args);
    void Fatal(object message);
    void Fatal(object message, Exception exception);
    void FatalFormat(string format, params object[] args);
    void Info(object message);
    void Info(object message, Exception exception);
    void InfoFormat(string format, params object[] args);
    void Warn(object message);
    void Warn(object message, Exception exception);
    void WarnFormat(string format, params object[] args);
}

Я также проверил, что создание Mocks вручную и настройка их с помощью Moq работает — свойство ILog правильно заменено моим Mock, используя:

myServiceMock.Setup(s => s.Logger).Returns(myLoggerMock)

Кто-нибудь может пролить свет на это?

Этапы воспроизведения

Проверить

    using ServiceStack;
    using ServiceStack.Logging;
    using ServiceStack.Web;
    using MyApp;

    [Test]
    public void LogTest()
    {
        var fixture = new Fixture().Customize(new AutoMoqCustomization());

        var log = fixture.Freeze<Mock<ILog>>();
        var request = fixture.Freeze<Mock<IRequest>>();
        var response = new Mock<IResponse>();
        var service = fixture.Create<MyService>();

        request.Setup(r => r.Response).Returns(response.Object);

        service.Post(null);

        log.Verify(l => l.Warn(It.IsAny<string>()), Times.Once());
    }

Класс обслуживания. Примечание. Обычно свойство Logger имеет суффикс = LogManager.GetLogger(typeof(MyService)), но я пропустил его на данном этапе, чтобы увидеть проблему.

using ServiceStack;
using ServiceStack.Logging;

namespace MyApp
{
  public class MyService : BaseService
  {
    public override ILog Logger { get; }

    public MyResponse Post(MyRequest request)
    {
        if (request != null) return new MyResponse() {Message = request.Message};

        Logger.Warn("Null request object");
        return null;
    }
   }

public abstract class BaseService : Service
{
    public abstract ILog Logger { get; }
}

public class MyRequest
{
    public string Message { get; set; }
}

public class MyResponse
{
    public string Message { get; set; }
}
}

Если поставить точку останова на строке service.Post(null), вы увидите, что свойство ILog по-прежнему имеет значение null, но другие свойства имеют моки.


person RNDThoughts    schedule 15.10.2015    source источник
comment
Является ли класс со свойством Logger конкретным классом? AutoMoq не проксирует конкретные классы: blog.ploeh.dk/2010/08/ 25/   -  person Mark Seemann    schedule 15.10.2015
comment
Привет, Марк, это конкретный класс, поэтому я понимаю вашу точку зрения, но если это так, то почему AutoMoq автоматически заполняет все остальные свойства того же класса макетами?   -  person RNDThoughts    schedule 15.10.2015
comment
Я не знаю, так как ты не показываешь мне репродукцию...   -  person Mark Seemann    schedule 15.10.2015
comment
Добавили шаги воспроизведения.   -  person RNDThoughts    schedule 15.10.2015
comment
Мои извинения, я сделал ложное предположение, что операторы использования указывают на то, что класс Service был из ServiceStack. Определение класса можно увидеть здесь: github.com/ServiceStack/ Стек служб/blob/master/src/   -  person RNDThoughts    schedule 16.10.2015
comment
Какую версию Moq вы используете? Возможно, вы столкнулись с этой ошибкой.   -  person Enrico Campidoglio    schedule 16.10.2015
comment
В этих свойствах много окружающего поведения. Это действительно вопрос AutoFixture или скорее вопрос ServiceStack? Можно ли воспроизвести поведение, вызывающее проблемы, без ServiceStack?   -  person Mark Seemann    schedule 16.10.2015
comment
Я не смог воспроизвести его на своих собственных POCO, поэтому вы, вероятно, правы в том, что это скорее вопрос ServiceStack. Однако, как я уже упоминал @Enrico, я подозреваю, что было бы более разумно реорганизовать мой код и протестировать вне класса ServiceStack.   -  person RNDThoughts    schedule 16.10.2015


Ответы (1)


Все, что делает AutoMoqCustomization, — это настраивает AutoFixture для делегирования всех запросов интерфейсов или абстрактных типов в Moq. Он не устанавливает автоматически какие-либо заглушки для свойств и методов создаваемых тестовых двойников.

Однако начиная с AutoFixture 3.20.0 существует новая настройка автомока, которая делает именно это — AutoConfiguredMoqCustomization:

var fixture = new Fixture().Customize(new AutoConfiguredMoqCustomization());

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

Но есть проблема. Как сообщил @dcastro, ошибка была представленный в Moq 4.2.1502.911, который приводит к тому, что свойства только для чтения — например, свойство MyService.Logger в вашем случае — переопределяются Moq после AutoFixture настроил их.

Таким образом, даже если вы переключитесь на AutoConfiguredMoqCustomization, свойство Logger будет по-прежнему иметь значение null в силу того, что оно доступно только для чтения. С другой стороны, другие свойства и методы с возвращаемыми значениями будут настроены на возврат объектов, созданных AutoFixture.

Лучше всего прямо сейчас начать использовать AutoConfiguredMoqCustomization и перейти на более раннюю версию Moq, где свойства только для чтения по-прежнему настраиваются правильно.

person Enrico Campidoglio    schedule 16.10.2015
comment
Спасибо за вклад, Энрико, я последовал вашему предложению и понизил рейтинг, но все равно получил тот же ответ (т.е. нулевое значение в свойстве Logger). Однако эти проблемы заставили меня пересмотреть структуру моего кода. Как отмечает @mark-seemann в своем блоге, вероятно, что-то в корне неверно в моей структуре кода, если мне приходится настраивать массу моков, чтобы что-то протестировать. Это не меняет того факта, что по какой-то причине я до сих пор не могу настроить тестовый дубль для этого конкретного свойства. - person RNDThoughts; 16.10.2015
comment
Ответ Энрико отмечен как правильный, хотя он не решает проблему, но указывает на проблему, которая может быть причиной. - person RNDThoughts; 21.10.2015