Использование 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)?
Ответы
Ответ 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() в конце каждой настройки? (Это действительно догадка, хотя я боюсь).