2016-02-01 2 views
1

Учитывая объект, я хотел бы создать макет, который реализует интерфейс объекта и издевается над одним методом, но пересылает остальные методы реальному объекту, не базовый класс.Как пересылать другой объект при использовании .NET Moq?

Например:

ISqlUtil sqlUtil = GetTheRealSqlUtilObjectSomehow(...); 
var mock = new Mock<ISqlUtil>(); 
mock.Setup(o => o.SpecialMethodToBeMocked(...)).Returns<...>(...) 
// Here I would like to delegate the rest of the methods to the real sqlUtil object. How ? 

Таким образом, в данном примере я хочу издеваться просто ISqlUtil.SpecialMethodToBeMocked вперед и остальные методы/свойства к существующему экземпляру sqlUtil.

Возможно ли это в Moq.NET?

EDIT 1

Он должен работать для общих методов, а также.

ответ

2

Вы не можете сделать это с помощью Moq из коробки. Тем не менее, я думаю, вы можете достичь в основном того, что хотите, если перейдете к следующему слою и напрямую используйте Castle DynamicProxy (это то, что находится под Moq).

Таким образом, учитывая следующий базовый код для имитации вашего вопроса (по существу, интерфейса, конкретную реализации и завода, потому что бетон трудно сделать/Настройка):

public interface ISqlUtil { 
    T SomeGenericMethod<T>(T args); 

    int SomeMethodToIntercept(); 
} 
public class ConcreteSqlUtil : ISqlUtil { 
    public T SomeGenericMethod<T>(T args){ 
     return args; 
    } 
    public int SomeMethodToIntercept() { 
     return 42; 
    } 
} 
public class SqlUtilFactory { 
    public static ISqlUtil CreateSqlUtil() { 
     var rVal = new ConcreteSqlUtil(); 
     // Some Complex setup 
     return rVal; 
    } 
} 

Вы можете иметь следующий тест:

public void TestCanInterceptMethods() { 
    // Create a concrete instance, using the factory 
    var coreInstance = SqlUtilFactory.CreateSqlUtil(); 

    // Test that the concrete instance works 
    Assert.AreEqual(42, coreInstance.SomeMethodToIntercept()); 
    Assert.AreEqual(40, coreInstance.SomeGenericMethod(40)); 

    // Create a proxy generator (you'll probably want to put this 
    // somewhere static so that it's caching works if you use it) 
    var generator = new Castle.DynamicProxy.ProxyGenerator(); 

    // Use the proxy to generate a new class that implements ISqlUtil 
    // Note the concrete instance is passed into the construction 
    // As is an instance of MethodInterceptor (see below) 
    var proxy = generator.CreateInterfaceProxyWithTarget<ISqlUtil>(coreInstance, 
           new MethodInterceptor<int>("SomeMethodToIntercept", 33)); 

    // Check that calling via the proxy still delegates to existing 
    // generic method 
    Assert.AreEqual(45, proxy.SomeGenericMethod(45)); 
    // Check that calling via the proxy returns the result we've specified 
    // for our intercepted method 
    Assert.AreEqual(33, proxy.SomeMethodToIntercept()); 
} 

метод перехватчик выглядит следующим образом:

public class MethodInterceptor<T> : Castle.DynamicProxy.IInterceptor { 
    private T _returns; 
    private string _methodName; 
    public MethodInterceptor(string methodName, T returns) { 
     _returns = returns; 
     _methodName = methodName; 
    } 
    public void Intercept(IInvocation invocation) { 
     if (invocation.Method.Name == _methodName) { 
      invocation.ReturnValue = _returns; 
     } 
     else { 
      invocation.Proceed(); 
     } 
    } 
} 

По сути, перехватчик проверяет, соответствует ли тот метод, который вас интересует, и если да, возвращает возвращаемое значение. В противном случае он вызывает Proceed, который делегирует вызов метода на конкретный объект, предоставленный при создании прокси.

В примере кода используются строки, а не лямбда, чтобы указать метод для перехвата, очевидно, это можно было бы изменить (упражнение для читателя). Кроме того, это не использование Moq, поэтому вы теряете элементы Setup, Returns и Verify, которые заменяются Interceptor, поэтому это может быть слишком далеко от того, что вам нужно, чтобы быть полезным, однако в зависимости от вашего кода похоже, что это может быть жизнеспособным альтернативным подходом.

+0

Это самое близкое к тому, что мне нужно, слишком плохо Moq не поддерживает его. – mark

0

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

var util = GetSqlUtil(); 

var mockUtil = new Mock<ISqlUtil>(MockBehavior.Strict); 
mockUtil.Setup(x => x.SomeCall(...)).Returns<...>(args => util.SomeCall(args)); 
+1

Эта часть я ставлю. Суть вопроса в том, что я не хочу этого делать. Это звучит как нечто, что может быть частью функциональности Moq. – mark

+0

Конечно, и это не то, что он делает, насколько я знаю. Я бы сказал, однако, что если вам требуется фактическая функциональность от ваших макетов, вы слишком сильно расширяете масштаб теста и проверяете неправильную вещь. Вы тестируете метод A, который принимает экземпляр интерфейса B и выполняет несколько вызовов. Вы хотите видеть, как ведет себя метод A. __if B возвращает определенные значения__. Вы действительно не заботитесь о том, чтобы сделать B __do that__, вы хотите убедиться, что A написано правильно для определенного случая. Выполните все необходимое для получения нужных результатов, не более того. –

+1

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

Смежные вопросы