Настройка Moq для интерфейса с аргументом action / function - PullRequest
4 голосов
/ 24 февраля 2011

Я пытаюсь смоделировать вызов к серверу и проверить, что проверенный код вызвал правильный метод. Структура кода выглядит следующим образом:

public interface IServerAdapter
{
    void CallServer(Action<IServerExposingToClient> methodToCall, out bool serverCalled);
    object CallServer(Func<IServerExposingToClient, object> methodToCall, out bool serverCalled);
}

public interface IServerExposingToClient
{
    Resource GetResource(string id);
    void AddResource(Resource resource);
}

Код, который я тестирую, обращается к реализации IServerAdapter и вызывает IServerExposingToClient методы на сервере. Причина этого в том, что нам не нужно реализовывать каждый отдельный метод (их довольно много) на IServerExposingToClient в классе. Поскольку это затем вызывается на прокси-сервере, в нашем коде это работает довольно хорошо.

Вызов метода на сервере выполняется следующим образом:

_mainViewModel.ServerAdapter.CallServer(m => m.AddResource(resource), out serverCalled);

Теперь проблема в тестировании и издевательствах. Мне нужно утверждать, что метод (AddResource) был вызван на сервере. out serverCalled (проблема 1) - убедиться, что вызов поступил на сервер, поэтому логика передается вызывающей стороне так, как должно.

Когда я использую следующую настройку Moq, я могу утверждать, что какой-то метод был вызван на сервере:

Mock<IServerAdapter> serverAdapterMock = new Mock<IServerAdapter>();
bool serverCalled;
bool someMethodCalled = false;
serverAdapterMock.Setup(c => c.CallServer(It.IsAny<Action<IServerExposingToClient>>(), out serverCalled)).Callback(() => someMethodCalled = true);
// assign serverAdapterMock.Object to some property my code will use
// test code
Assert.IsTrue(someMethodCalled, "Should have called method on server.");

Как говорит тест, я могу утверждать, что на сервере был вызван какой-то метод. Проблема в том, что я не знаю , какой метод был вызван. В некоторых методах логика, которую я хочу протестировать, может принимать разные пути в зависимости от условий, а для некоторых сценариев несколько путей могут привести к вызову сервера. Поэтому мне нужно знать, какой метод был вызван, чтобы в моем тесте были правильные утверждения (проблема 2).

Переменная serverCalled должна быть установлена ​​в соответствии с сигнатурой для вызова метода в макете IServerAdapter. Я также очень хотел бы иметь возможность установить эту переменную внутри некоторого обратного вызова или чего-либо еще, что позволило бы логике кода, который я тестирую, течь так, как должно (проблема 1). Из-за того, как он работает сейчас, serverCalled не будет установлен в mock setup.

Основная проблема не в том, чтобы знать, какой метод был вызван для сервера. Я пытался использовать Match для проверки имени метода делегата, но без сигары.

serverAdapterMock.Setup(c => c.CallServer(Match.Create<Action<IServerExposingToClient>>( x => IsAddResource(x)), out serverCalled)).Callback(() => someMethodCalled = true);

Когда вызывается IsAddResource, функция не ссылается на AddResource, только где она была создана и с каким аргументом она вызывается.

Кто-нибудь знает, как проверить, какой метод был вызван?

Что касается другой проблемы с out serverCalled, вы можете утверждать, что метод void может быть вместо bool, и что другой метод может вернуть null, если у нас нет соединения с сервером ( последний аргумент будет неоднозначным, поскольку значение null может означать, что объект не существует или сервер недоступен).

Я бы очень хотел получить предложения по решению обеих проблем.

Заранее спасибо

1 Ответ

6 голосов
/ 25 февраля 2011

Проблема первая:

Как вы указали, обратный вызов здесь был бы идеальным.К сожалению, поскольку сигнатура вашего метода имеет параметр out , он в настоящее время не может быть перехвачен Moq.На форумах Moq есть сообщение , которое похоже на вашу проблему.Нет никаких указаний на то, что он будет адресован.

Если вы готовы изменить сигнатуру вашего метода, рассмотрите возможность удаления параметра out - они все равно пахнут.Все было бы намного проще, если бы вы просто сгенерировали исключение, когда сервер был недоступен.Получив это из оттуда (каламбур) откроет ваш Обратный вызов для решения вашей второй проблемы.

Задача вторая:

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

Вы можете получить имя метода, используя что-то вроде:

public string GetMethodName(Expression<Action<IServerExposingToClient>> exp)
{
    return ((ExpressionMethodCall)exp.Body).Method.Name;
}

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

Например:

[Test]
public void WhenTheViewModelIsLoaded_TheSystem_ShouldRecordOneNewResource()
{
   // arrange
   var serverAdapterMock = new Mock<IServerAdapter>();
   var subject = new SubjectUnderTest( serverAdapterMock.Object );

   // act
   subject.Initialize();

   // assert
   serverAdapterMock.Verify( c => c.CallServer( IsAddResource() ), Times.Exactly(1));
}

static Expression<Action<IServerExposedToClient>> IsAddResource()
{
    return Match.Create<Expression<Action<IServerExposedToClient>>>(
              exp => GetMethodName(exp) == "AddResource");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...