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

Проверка метода была вызвана

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

public class TestClass
{
    public string Say()
    {
        return Hello();
    }

    internal virtual string Hello()
    {
        return "";
    }
}

[TestMethod]
public void Say_WhenPublic_CallsHello()
{
    Mock<TestClass> mock = new Mock<TestClass>();
    mock.Setup(x => x.Hello()).Returns("Hello World");

    string result = mock.Object.Say();
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);     
}

Что не удается с этим сообщением:

Say_WhenPublic_CallsHello не удалось: Moq.MockException: Вызов не выполнялся на макет 1 раз: x = > x.Hello() в Moq.Mock.ThrowVerifyException(ожидаемый IProxyCall, выражение Expression, времена раз)...

Если я сделаю общедоступным метод Hello, тест пройдет. В чем проблема?

public virtual string Hello()
{
    return "";
}

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

4b9b3361

Ответ 1

Тест терпит неудачу, когда Hello() является внутренним, поскольку Moq не может обеспечить переопределение метода в этом случае. Это означает, что внутренняя реализация Hello() будет запущена, а не макет версии, в результате чего Verify() завершится с ошибкой.

Кстати, то, что вы здесь делаете, не имеет смысла в контексте unit test. A unit test не волнует, что Say() вызывает внутренний метод Hello(). Это реализация, внутренняя для вашей сборки, а не проблема потребления кода.

Ответ 2

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

В вашем примере вы вызываете метод Say(), и он возвращает ожидаемый текст. Ваше ожидание должно не обеспечивать выполнение конкретной реализации Say(), т.е. Вызывает внутренний метод Hello() для возврата этой строки. Вот почему он не передает проверку, а также почему возвращаемая строка является "", т.е. была вызвана фактическая реализация Hello().

Предоставляя общедоступный метод Hello, это означает, что это позволило Moq перехватить вызов на него и вместо этого использовать его. Поэтому в этом случае тест, похоже, пройдет. Однако в этом случае вы действительно ничего не сделали, потому что в вашем тесте говорится, что при вызове Say() результатом является "Hello World", когда в acutal факте результат "".

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

public interface IHelloProvider
{
    string Hello();
}

public class TestClass
{
    private readonly IHelloProvider _provider;

    public TestClass(IHelloProvider provider)
    {
        _provider = provider;
    }

    public string Say()
    {
        return _provider.Hello();
    }
}

[TestMethod]
public void WhenSayCallsHelloProviderAndReturnsResult()
{
    //Given
    Mock<IHelloProvider> mock = new Mock<IHelloProvider>();
    TestClass concrete = new TestClass(mock.Object);
    //Expect
    mock.Setup(x => x.Hello()).Returns("Hello World");
    //When
    string result = concrete.Say();
    //Then
    mock.Verify(x => x.Hello(), Times.Exactly(1));
    Assert.AreEqual("Hello World", result);
}

В моем примере я представил интерфейс для IHelloProvider. Вы заметите, что реализация IHelloProvider не выполняется. Это лежит в основе того, чего мы пытаемся достичь, используя насмешливое решение.

Мы пытаемся протестировать класс (TestClass), который зависит от чего-то внешнего (IHelloProvider). Если вы используете Test Driven Development, вы, вероятно, еще не написали IHelloProvider, но знаете, что вам понадобится один в какой-то момент. Сначала вы хотите, чтобы TestClass работал, а не отвлекался. Или, возможно, IHelloProvider использует базу данных или плоский файл, или его сложно настроить.

Даже если у вас есть полностью работающий IHelloProvider, вы по-прежнему пытаетесь проверить поведение TestClass, поэтому использование конкретного HelloProvider скорее всего сделает ваш тест более подверженным сбою, например, если есть изменение в поведении из HelloProvider, вы не хотите менять тесты каждого класса, который его использует, вы просто хотите изменить тесты HelloProvider.

Возвращаясь к коду, теперь у нас есть класс TestClass, который зависит от интерфейса IHelloProvider, реализация которого предоставляется во время построения (это инъекция зависимостей).

Поведение Say() заключается в том, что он вызывает метод Hello() на IHelloProvider.

Если вы посмотрите на тест, мы создали фактический объект TestClass, так как мы действительно хотим протестировать код, который мы написали. Мы создали Mock IHelloProvider и сказали, что ожидаем, что он вызовет метод Hello(), а когда он вернет строку "Hello World".

Затем мы вызываем Say() и проверяем результаты по-прежнему.

Важно понять, что нас не интересует поведение IHelloProvider, и поэтому мы можем издеваться над ним, чтобы упростить тестирование. Мы заинтересованы в поведении TestClass, поэтому мы создали фактический TestClass, а не Mock, чтобы мы могли проверить его фактическое поведение.

Надеюсь, это помогло прояснить, что происходит.

Ответ 3

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

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

Частичное издевательство полезно только тогда, когда вы хотите проверить функциональность абстрактного класса с издевательскими реализациями абстрактных методов. Если вы этого не сделаете, вы, скорее всего, не увидите большой выгоды от частичного макета.

Если это не абстрактный класс, вам нужно больше сосредоточиться на подходе Модана, который говорит, что вы должны издеваться над зависимостями, а не от своего целевого класса. Все это сводится к правилу не издеваться над тем, что вы тестируете.