Настройка Moq для интерфейса с аргументом action/function

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

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 если у нас нет соединения с сервером (последний аргумент будет неоднозначным, так как ноль может означать, что объект не существует или сервер недоступен).

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

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

1 ответ

Решение

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

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

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

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

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

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

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");
}
Другие вопросы по тегам