Мокинг объектов с помощью Moq, когда у конструктора есть параметры

94

У меня есть объект, который я пытаюсь высмеять с помощью moq. У конструктора объекта есть обязательные параметры:

public class CustomerSyncEngine {
    public CustomerSyncEngine(ILoggingProvider loggingProvider, 
                              ICrmProvider crmProvider, 
                              ICacheProvider cacheProvider) { ... }
}

Теперь я пытаюсь создать макет для этого объекта, используя синтаксис moq v3 «setup» или v4 «Mock.Of», но не могу понять этого ... все, что я пытаюсь, не проверяется. Вот что у меня есть до сих пор, но последняя строка дает мне реальный объект, а не макет. Причина, по которой я это делаю, состоит в том, что у меня есть методы в CustomerSyncEngine, которые я хочу проверить, вызываются ...

// setup
var mockCrm = Mock.Of<ICrmProvider>(x => x.GetPickLists() == crmPickLists);
var mockCache = Mock.Of<ICacheProvider>(x => x.GetPickLists() == cachePickLists);
var mockLogger = Mock.Of<ILoggingProvider>();

// need to mock the following, not create a real class like this...
var syncEngine = new CustomerSyncEngine(mockLogger, mockCrm, mockCache);
Эндрю Коннелл
источник
Можете ли вы предоставить образец метода, который вы хотите проверить, когда вас вызывают?
Ciaran
4
Так что, если у меня есть зависимости от классов, а не от интерфейсов, мне нужно высмеивать даже их зависимости, это происходит рекурсивно. В конце концов, я вынужден использовать некоторые интерфейсы, чтобы мой код оставался тестируемым, даже если мне не нужны интерфейсы в моем коде. Я думаю, что слишком много интерфейсов - это больший запах, чем насмешка над конкретными классами ...
Тарион

Ответы:

34

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

Вы должны использовать Mock.Of<CustomerSyncEngine>()

Единственная проблема с типами Mocking Concrete заключается в том, что для Moq потребуется общедоступный конструктор по умолчанию (без параметров) ИЛИ вам нужно создать Moq со спецификацией конструктора arg. http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Лучше всего щелкнуть правой кнопкой мыши свой класс и выбрать «Извлечь интерфейс».

Рагху
источник
3
Что касается проблемы, альтернативой является использование контейнера AutoMocking. Мне больше всего нравится Machine.Fakes в сочетании с Machine.Specifications, использование автомобильного контейнера облегчает тестирование небольших площадей. Предположим, Эндрю нужно было протестировать метод, CustomerSyncEngineкоторый используется только ICrmProviderс традиционными реализациями имитаций, которые должны быть предоставлены для всех трех интерфейсов, тогда как контейнер autmocking позволит вам предоставить только один.
Крис Марисич
74

Измените последнюю строку на

var syncEngine = new Mock<CustomerSyncEngine>(mockLogger, mockCrm, mockCache).Object;

и это должно работать

Сухас
источник
3
Не знаете, как этот комментарий относится к моему ответу?
Сухас
2
Потому что это вызовет ошибку компиляции, поскольку mockLogger и другие выдадут исключение, что у них нет свойства Object
Джастин Пихони,
2
Поскольку OP использует Mock.Of <T> () для создания макетов типов регистратора, crm и кеша, возвращаемый объект возвращается как T, а не как Mock <T>. Итак, mockLogger.Object и т. Д. Не требуется при передаче их в Mock of CustomerSyncEngine и, как упоминалось в @JustinPihony, должен показать вам ошибку времени разработки.
Джош Гаст
1
@suhas не должно быть егоnew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB
@GiriB не требуется, но возможно, поскольку макет определяется с помощью Params. public Mock (params object [] args)
Jiří Herník