Имитация возвращаемых значений конкретного метода класса с использованием Moq

#c# #unit-testing #moq #abstract-class #concreteclass

#c# #модульное тестирование #moq #абстрактный класс #concreteclass

Вопрос:

У меня есть такая абстрактная фабрика.

 public abstract class AbstractFactory
{
    public abstract ISyncService GetSyncService(EntityType entityType);
}
  

И у меня есть его конкретная реализация, подобная этой.

 public class SyncFactory : AbstractFactory
{
    private readonly IOperatorRepository _operatorRepository;

    public SyncFactory( IOperatorRepository operatorRepository)
    {
        _operatorRepository = operatorRepository;
    }

    public override ISyncService GetSyncService(EntityType entityType)
    {            
            return new OperatorSyncService(_operatorRepository);           
    }
}
  

Доступ к этой конкретной фабрике осуществляется с помощью метода, подобного этому.

 public void MethodTobeTested()
{
    var syncService =
                new SyncFactory(_operatorRepository).GetSyncService(entityType);
}
  

Теперь мне нужно написать модульный тест для MethodTobeTested() .

Я издевался над возвращаемым значением GetSyncService() следующим образом. Но он вызывает фактический OperatorSyncService, а не макет. Мне нужен этот макет, чтобы имитировать другой метод внутри OperatorSyncService

 private Mock<SyncFactory> _syncServiceMock;
_syncServiceMock = new Mock<SyncFactory>();

_syncServiceMock.Setup(m => m.GetSyncService(operator)).Returns(_operatorSyncServiceMock.Object);
  

Есть идеи о том, как это решить?

Комментарии:

1. Почему бы просто не издеваться над IOperatorRepository и не вызвать метод

2. MethodToBeTested тесно связано с SyncFactory тем, что метод вручную создает новый экземпляр SyncFactory . Это очень затрудняет имитацию зависимости.

Ответ №1:

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

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

 var syncService =
            new SyncFactory(_operatorRepository).GetSyncService(entityType);
  

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

  1. В конструкторе класса, который содержит MethodToBeTested, добавьте параметр, который принимает экземпляр вашей абстрактной фабрики, затем заставьте ваш MethodToBeTested использовать эту внедренную фабрику вместо создания новой, это позволит вам издеваться над всей фабрикой — это мой рекомендуемый подход, потому что класс, содержащий MethodToBeTested, больше не будет использоваться.необходимо знать, как создать экземпляр factory, чего не должно быть, если вы следуете принципу единой ответственности. Не было бы никакой зависимости от какой-либо конкретной реализации.

  2. Как и выше, но вместо того, чтобы вводить IOperatorRepository, а не factory, затем вы можете ввести макет IOperatorRepository, но я бы не советовал этого делать, поскольку вы проделали такую хорошую работу по созданию всех своих абстракций, чтобы затем отбросить эту работу и «создать» экземпляр SyncFactory и создать конкретную зависимость

Ответ №2:

MethodToBeTested тесно связано с SyncFactory тем, что метод вручную создает новый экземпляр SyncFactory . Это очень затрудняет имитацию зависимости.

Предполагая

 public class ClassToBeTested {

    public void MethodTobeTested() {
        var syncService = new SyncFactory(_operatorRepository).GetSyncService(entityType);
        //...other code
    }

}
  

ClassToBeTested должно быть преобразовано в

 public class ClassToBeTested {
    private readonly AbstractFactory syncFactory;

    public ClassToBeTested (AbstractFactory factory) {
        this.syncFactory = factory
    }

    public void MethodTobeTested() {
        var syncService = syncFactory.GetSyncService(entityType);
        //...other code
    }

}
  

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

Ответ №3:

new В методе MethodTobeTested создается новый экземпляр, поэтому макет не может быть введен. Введите factory, например, в качестве параметра, чтобы его можно было подделать в тесте.

 public void MethodTobeTested(AbstractFactory factory)
{
    EntityType entityType = null;
    var syncService = factory.GetSyncService(entityType);
}

[TestMethod]
public void Method_Condition_Result()
{
    // Arrange
    TestedClass tested = new TestedClass();
    Mock<ISyncService> syncServiceMock = new Mock<ISyncService>();
    Mock<AbstractFactory> factoryMock = new Mock<AbstractFactory>();
    factoryMock.Setup(f => f.GetSyncService(It.IsAny<EntityType>())).Returns(syncServiceMock.Object);

    // Act
    tested.MethodTobeTested(factoryMock.Object);

    // Assert
    // ...
}