Сбросить макет проверки в Moq? - PullRequest
51 голосов
/ 12 ноября 2010

Настройка как таковая:

public interface IFoo
{
    void Fizz();
}

[Test]
public void A()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();

    foo.Verify(x => x.Fizz());

    // stuff here

    foo.Verify(x => x.Fizz(), Times.Never()); // currently this fails
}

Обычно я хотел бы ввести код на // stuff here, чтобы пройти foo.Verify(x => x.Fizz(), Times.Never()).

И поскольку это, вероятно, представляет собой злоупотребление тестированием moq / unit, я оправдываюсь тем, что могу сделать что-то вроде этого:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    // reset the verification here

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}

По сути, у меня есть объект состояния, в котором требуется немало усилий (как с точки зрения создания различных фиктивных объектов, так и других сглаживаний), чтобы перенести его в State1. Затем я хочу проверить переход из State1 в State2. Вместо дублирования или абстрагирования кода я бы предпочел просто повторно использовать тест State1, вставить его в State2 и выполнить мои утверждения - все, что я могу сделать, кроме вызовов проверки.

Ответы [ 8 ]

107 голосов
/ 08 мая 2014

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

С помощью этого метода вы можете делать именно то, что вы хотели, как показано ниже:

[Test]
public void Justification()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);
    foo.Setup(x => x.Fizz());

    var objectUnderTest = new ObjectUnderTest(foo.Object);

    objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());

    foo.ResetCalls(); // *** Reset the verification here with this glorious method ***

    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code

    foo.Verify(x => x.Fizz(), Times.Never());
}

Обновление

Теперь вместо .ResetCalls () мы должны использовать .Invocations.Clear () в последней версии библиотеки:

foo.Invocations.Clear()
7 голосов
/ 12 ноября 2010

Я не думаю, что вы можете сбросить макет, как это. Вместо этого, если вы знаете, что Fizz необходимо вызвать один раз при переходе в состояние 1, вы можете выполнить свои проверки следующим образом:

objectUnderTest.DoStuffToPushIntoState1();
foo.Verify(x => x.Fizz(), Times.Once());  // or however many times you expect it to be called

objectUnderTest.DoStuffToPushIntoState2();
foo.Verify(x => x.Fizz(), Times.Once());

Сказав это, я все равно создал бы два отдельных теста для этого. Как два теста, легче увидеть, происходит ли переход в состояние 1 или переход в состояние 2. Кроме того, при совместном тестировании, подобном этому, если ваш переход в состояние 1 завершается неудачно, метод тестирования завершается, а ваш переход в состояние 2 не проходит проверку.

Редактировать

В качестве примера я протестировал следующий код с xUnit:

[Fact]
public void Test()
{
    var foo = new Mock<IFoo>(MockBehavior.Loose);

    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed After State 1");

    // stuff here
    foo.Object.Fizz();
    foo.Verify(x => x.Fizz(), Times.Once(), "Failed after State 2"); 
}

Этот тест завершается неудачно с сообщением «Ошибка после состояния 2». Это имитирует то, что произойдет, если ваш метод, который помещает foo в State 2, вызывает Fizz. Если это произойдет, второй Verify не удастся.

Рассматривая свой код еще раз, поскольку вы вызываете один метод, чтобы проверить, вызывает ли он / не вызывает другой метод на макете, я думаю, вам нужно установить CallBase в true, чтобы базовая DoStuffToPushIntoState2 была называется, а не отменять насмешку.

4 голосов
/ 24 августа 2012

Я также был свидетелем сбоя проверки Times.Exactly (1) в модульных тестах с использованием MoQ, с сообщением об ошибке «был вызван 2 раза». Я вижу это как ошибку в MoQ, так как я ожидал бы чистых ложных состояний при каждом запуске теста.

Моя работа заключалась в том, чтобы назначить новый макет и цель теста в настройках теста.

private Mock<IEntityMapper> entityMapperMock;
private OverdraftReportMapper target;

[SetUp]
public void TestSetUp()
{
  entityMapperMock = new Mock<IEntityMapper>();
  target = new OverdraftReportMapper(entityMapperMock.Object);
} 
3 голосов
/ 02 августа 2016

Зависит от того, какую версию Mock вы используете, я точно знаю, что мы можем сделать это

someMockObject.ResetCalls();
3 голосов
/ 08 августа 2012

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

Это показано на странице Moq Quick Start , таким образом:

// returning different values on each invocation
var mock = new Mock<IFoo>();
var calls = 0;
mock.Setup(foo => foo.GetCountThing())
    .Returns(() => calls)
    .Callback(() => calls++);
// returns 0 on first invocation, 1 on the next, and so on
Console.WriteLine(mock.Object.GetCountThing());
2 голосов
/ 03 августа 2018

Дополнение к ответу @stackunderflow (хаха, хороший ник :))

В более поздних версиях Moq, Moq.MockExtensions.ResetCalls() был помечен как устаревший. mock.Invocations.Clear() следует использовать вместо:

foo.Invocations.Clear();
2 голосов
/ 14 ноября 2010

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

Более чем рабочий код, тестовый код следует оптимизировать для удобства чтения и изоляции.Проверка одного аспекта поведения системы не должна влиять на другие аспекты.Действительно, намного проще преобразовать общий код в метод установки, чем пытаться сбросить фиктивные объекты.

ObjectUnderTest _objectUnderTest;

[Setup] //Gets run before each test
public void Setup() {
    var foo = new Mock<IFoo>(); //moq by default creates loose mocks
    _objectUnderTest = new ObjectUnderTest(foo.Object);
}
[Test]
public void DoStuffToPushIntoState1ShouldCallFizz() {
    _objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

    foo.Verify(x => x.Fizz());
}
[Test]
public void DoStuffToPushIntoState2ShouldntCallFizz() {
{
    objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
    foo.Verify(x => x.Fizz(), Times.Never());
}
0 голосов
/ 19 сентября 2013

Следующий подход отлично работает для меня (используя Moq.Sequence)

    public void Justification()
    {
        var foo = new Mock<IFoo>(MockBehavior.Loose);
        foo.Setup(x => x.Fizz());

        var objectUnderTest = new ObjectUnderTest(foo.Object);

        objectUnderTest.DoStuffToPushIntoState1(); // this is various lines of code and setup

        foo.Verify(x => x.Fizz());

        // Some cool stuff

        using (Sequence.Create())
        {
            foo.Setup(x => x.Fizz()).InSequence(Times.Never())
            objectUnderTest.DoStuffToPushIntoState2(); // more lines of code
        }
    }

Дайте мне знать, сработало ли это для вас

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...