Подтвердить что ты не робот

Использование Moq для проверки звонков производится в правильном порядке

Мне нужно протестировать следующий метод:

CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more Write()s...
}

Я создал Moq'd IWriter, и я хочу, чтобы методы Write() вызывались в правильном порядке.

У меня есть следующий тестовый код:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));

Однако второй вызов Write() в CreateOutput() (для записи значения id) вызывает a MockException с сообщением "IWriter.Write() invocation failed with mock behavior Strict. Все вызовы на mock должен иметь соответствующую настройку.".

Мне также трудно найти какую-либо окончательную, последнюю документацию/примеры последовательности Moq.

Я делаю что-то неправильно, или я не могу настроить последовательность, используя тот же метод? Если нет, есть ли альтернатива, которую я могу использовать (предпочтительно используя Moq/NUnit)?

4b9b3361

Ответ 1

Есть ошибка, когда используя MockSequence в том же макете. Он определенно будет исправлен в более поздних версиях библиотеки Moq (вы также можете исправить это вручную, изменив реализацию Moq.MethodCall.Matches).

Если вы хотите использовать только Moq, вы можете проверить порядок вызова метода через обратные вызовы:

int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));

Ответ 2

Мне удалось получить поведение, которое я хочу, но он требует загрузки сторонней библиотеки из http://dpwhelan.com/blog/software-development/moq-sequences/

Затем последовательность может быть протестирована с использованием следующего:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
    mockWriter.Setup(x => x.Write(expectedType)).InSequence();
    mockWriter.Setup(x => x.Write(expectedId)).InSequence();
    mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}

Я добавил это в качестве ответа частично, чтобы помочь документировать это решение, но меня все еще интересует, можно ли достичь чего-то подобного с помощью только Moq 4.0.

Я не уверен, что Moq все еще находится в разработке, но исправление проблемы с помощью MockSequence или включение расширения moq-последовательностей в Moq было бы хорошо видеть.

Ответ 3

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

public static class MockExtensions
{
  public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
  {
    // All closures have the same instance of sharedCallCount
    var sharedCallCount = 0;
    for (var i = 0; i < expressions.Length; i++)
    {
      // Each closure has it own instance of expectedCallCount
      var expectedCallCount = i;
      mock.Setup(expressions[i]).Callback(
        () =>
          {
            Assert.AreEqual(expectedCallCount, sharedCallCount);
            sharedCallCount++;
          });
    }
  }
}

Он работает, пользуясь тем, как замыкания работают в отношении переменных с областью. Поскольку для sharedCallCount существует только одно объявление, все блокировки будут иметь ссылку на одну и ту же переменную. С expectedCallCount новый экземпляр создается каждой итерацией цикла (в отличие от простого использования я в закрытии). Таким образом, каждое закрытие имеет экземпляр i, охваченный только для сравнения с sharedCallCount при вызове выражений.

Здесь маленький unit test для расширения. Обратите внимание, что этот метод вызывается в разделе настройки, а не в разделе вашего утверждения.

[TestFixture]
public class MockExtensionsTest
{
  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called in order
    mock.Object.MyMethod("1");
    mock.Object.MyMethod("2");
  }

  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called out of order
    Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
  }
}

public interface IAmAnInterface
{
  void MyMethod(string param);
}

Ответ 4

Недавно я собрал две функции для Moq: VerifyInSequence() и VerifyNotInSequence(). Они работают даже с Loose Mocks. Однако они доступны только в fork-хранилище moq:

https://github.com/grzesiek-galezowski/moq4

и ожидайте больше комментариев и тестирования, прежде чем принимать решение о том, могут ли они быть включены в официальный релиз moq. Однако ничто не мешает вам загружать исходный код в ZIP, создавая его в dll и давая ему попробовать. Используя эти функции, требуемая последовательность проверки может быть записана следующим образом:

var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() };

//perform the necessary calls

mockWriter.VerifyInSequence(x => x.Write(expectedType));
mockWriter.VerifyInSequence(x => x.Write(expectedId));
mockWriter.VerifyInSequence(x => x.Write(expectedSender));

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

Если вы решите попробовать эту экспериментальную функцию, прокомментируйте свои мысли по: https://github.com/Moq/moq4/issues/21

Спасибо!

Ответ 5

Простейшим решением будет использование Queue:

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));

Ответ 6

Я подозреваю, что ожидается, что это не то, что вы ожидаете.

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

Извините за отсутствие совета Moq напрямую. Я люблю это, но не сделал этого в нем.

Возможно, вам нужно добавить .Verify() в конце каждой настройки? (Это действительно догадка, хотя я боюсь).