Насмешка над вызовом метода базового класса с помощью Moq

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

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

6 ответов

По состоянию на 2013 год с последним Moq вы можете. Вот пример

public class ViewModelBase
{
    public virtual bool IsValid(DateTime date)
    {
        //some complex shared stuff here
    }
} 

public class MyViewModel : ViewModelBase
{
    public void Save(DateTime date)
    {
        if (IsValid(date))
        {
            //do something here
        }
    }
}

public void MyTest()
{
    //arrange
    var mockMyViewModel = new Mock<MyViewModel>(){CallBase = true};
    mockMyViewModel.Setup(x => x.IsValid(It.IsAny<DateTime>())).Returns(true);

    //act
    mockMyViewModel.Object.Save();

    //assert
    //do your assertions here
} 

Если я правильно понимаю ваш вопрос, у вас есть класс A, определенный в какой-то другой сборке, а затем класс B, реализованный более или менее так:

public class B : A
{
    public override MyMethod(object input)
    {
        // Do something
        base.MyMethod(input);
    }
}

А теперь вы хотите проверить, что base.MyMethod называется?

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

В вашем случае вы не можете очень хорошо попросить Moq наследовать от A, так как вы хотите проверить B.

Это означает, что вы должны попросить Moq дать вам Mock<B>, Однако это означает, что испускаемый тип является производным от B, и хотя он может переопределять MyMethod (который все еще является виртуальным) и вызывать его базу (B.MyMethod), он не может добраться до исходного класса и проверить, что B вызывает base.MyMethod,

Представьте, что вам нужно написать класс (C), производный от B. Хотя вы можете переопределить MyMethod, вы не сможете проверить, что B вызывает A:

public class C : B
{
    public override MyMethod(object input)
    {
        // How to verify that base calls its base?
        // base in this context means B, not A
    }
}

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

Однако я предполагаю, что вызов базового метода, который вы пытаетесь проверить, имеет некоторый наблюдаемый побочный эффект, поэтому, если возможно, можете ли вы использовать тестирование на основе состояния вместо тестирования на основе поведения для проверки результата вызова метода?

В любом случае, тестирование на основе состояния должно быть вашим подходом по умолчанию в большинстве случаев.

Согласитесь с Марком, это невозможно с помощью Moq.

В зависимости от вашей ситуации вы можете рассмотреть переход от наследования к составу. Тогда вы сможете смоделировать зависимость и проверить свой метод. Конечно, в некоторых случаях это может не стоить того.

Оберните метод базового класса в метод и настройте этот метод, например

public class B : A
{
    public virtual BaseMyMethod(object input)
    {
        // Do something
        base.MyMethod(input);
    }    
public override MyMethod(object input)
    {
        // Do something
        BaseMyMethod(input);
    }
}

а теперь настройте BaseMyMethod

Вполне возможен насмешливый базовый класс. Но вам придется изменить целевой класс.

Например DerivedClass расширяет BaseClass. BaseClass имеет методы MethodA (), MethodB (), MethodC ()... DerivedClass имеет этот метод:

void MyMethod() {
  this.MethodA();
  this.MethodB();
  this.MethodC();
}

Вы хотите смоделировать базовый класс, чтобы проверить, что все MethodA (), MethodB (), MethodC () вызываются внутри MyMethod ().

Вы должны создать поле в DerivedClass:

class DerivedClass {
  private BaseClass self = this;
  ...
}

А также Вы должны изменить MyMethod ():

void MyMethod() {
  self.MethodA();
  self.MethodB();
  self.MethodC();
}

Также добавьте метод, который может внедрить поле this.self с объектом Mock

public void setMock(BaseClass mock) {
  this.self = mock;
}

Теперь вы можете издеваться:

DerivedClass target = new DerivedClass ();
BaseClass  mock = new  Mock(typeof(BaseClass));
target.setMock(mock);
target.MyMethod();

mock.verify(MethodA);
mock.verify(MethodB);
mock.verify(MethodC);

Используя эту технику, вы также можете высмеивать вызовы вложенных методов.

Я нашел это решение - некрасиво, но оно могло работать.

var real = new SubCoreClass();                        
var mock = new Mock<SubCoreClass>();
mock.CallBase = true;

var obj = mock.Object;

mock
   .Setup(c => c.Execute())
   .Callback(() => 
      {                                                                       
         obj.CallBaseMember(typeof(Action), real, "Execute");             
         Console.WriteLine(obj.GetHashCode());
      }
      );

public static Delegate CreateBaseCallDelegate(object injectedInstance, Type templateDelegate, object instanceOfBase, string methodName)
{
   var deleg = Delegate.CreateDelegate(templateDelegate, instanceOfBase, methodName);
   deleg.GetType().BaseType.BaseType.GetField("_target", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(deleg, injectedInstance);

   return deleg;
}

public static object CallBaseMember(this object injectedInstance, Type templateDelegate, object instanceOfBase, string methodName, params object[] arguments)
{
   return CreateBaseCallDelegate(injectedInstance, templateDelegate, instanceOfBase, methodName).DynamicInvoke(arguments);
}
Другие вопросы по тегам