Глузування методів розширення з Moq


174

У мене є вже існуючий інтерфейс ...

public interface ISomeInterface
{
    void SomeMethod();
}

і я розширив цю взаємодію за допомогою mixin ...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

У мене є клас, який називає це, що я хочу перевірити ...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

і тест, де я хотів би знущатися над інтерфейсом і перевіряти виклик методу розширення ...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

Однак, якщо цей тест створює виняток ...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

Моє запитання: чи є приємний спосіб знущатись із дзвінка з міксину?


3
На мій досвід, терміни mixin та методи продовження - це окремі речі. Я б використовував останній у цьому випадку, щоб уникнути мішань: P
Ruben Bartelink

Відповіді:


33

Ви не можете "безпосередньо" знущатися над статичним методом (отже, методом розширення) з глузуючою рамкою. Ви можете спробувати Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ) - безкоштовний інструмент від Microsoft, який реалізує інший підхід. Ось опис інструменту:

Moles - це легкий каркас для тестових заглушок і об’їздів у .NET, який базується на делегатах.

Молі можуть використовуватися для обходу будь-якого методу .NET, включаючи невіртуальний / статичний методи в герметичних типах.

Ви можете використовувати Moles з будь-якою рамкою тестування (це не залежить від цього).


2
Окрім Moles, існують інші (невільні) схеми знущань, які використовують API-програмування .NET для знущання над об'єктами і таким чином можуть замінити будь-які дзвінки. Двоє, яких я знаю, - це JustMock і ізолятор TypeMock Telerik .
Марсель Госселін

6
Молі в теорії хороші, але я виявив три проблеми, коли я випробував це, що зупинив мене з його використанням ... 1) Він не працює в Resharper NUnit бігуні 2) Вам потрібно вручну створити моль-збірку для кожного стертого монтажу 3 ) Вам потрібно вручну відтворити збірку кротів, коли змінюється стертий метод.
Рассел Гіддінгс

26

Я використав Wrapper, щоб подолати цю проблему. Створіть об’єкт обгортки та передайте свій глузливий метод.

Дивіться глузуючі статичні методи для одиничного тестування Пола Ірвіна, це є приємні приклади.


12
Мені подобається ця відповідь, тому що те, що вона говорить (не прямо кажучи це), - це вам потрібно змінити свій код, щоб зробити його перевіреним. Ось так воно і працює. Зрозумійте, що в дизайні microchip / IC / ASIC ці мікросхеми повинні бути не тільки розроблені для роботи, але ще більше розроблені для перевірки, адже якщо ви не можете протестувати мікрочіп, він марний - ви не можете гарантувати, що він буде робота. Те саме стосується програмного забезпечення. Якщо ви не створили його для перевірки, це ... марно. Створіть його для перевірки, що в деяких випадках означає перезапис коду (і використання обгортки), а потім побудуйте автоматизовані тести, які його перевіряють.
Майкл Плаут

2
Я створив невелику бібліотеку, в яку загортаються Dapper, Dapper.Contrib і IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Дрю Сумідо

15

Я виявив, що мені потрібно було виявити внутрішню частину методу розширення, над яким я намагався знущатися над входом, і знущатися над тим, що відбувається всередині розширення.

Я розглядав, використовуючи розширення, як додавання коду безпосередньо до вашого методу. Це означало, що мені потрібно знущатися над тим, що відбувається всередині розширення, а не над самим розширенням.


11

Ви можете знущатися з тестового інтерфейсу, який успадковується від реального та має член з такою ж підписом, що і метод розширення.

Потім можна знущатися з тестового інтерфейсу, додавати справжній до макету та викликати метод тестування в налаштуваннях.

Тоді ваша реалізація макету може викликати будь-який метод, який ви хочете, або просто перевірити, чи називається метод:

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

Завдяки рішенню Håvard S в цій публікації за те, як реалізувати макет, який підтримує два інтерфейси. Одного разу я знайшов це, адаптувавши його до тестового інтерфейсу та статичного методу - це пішохідна прогулянка.


Чи можете ви показати нам, будь ласка, той самий зразок підпису для SomeNotAnExtensionMethodта SomeNotAnExtensionMethod? Тепер у мене ідея, як створити підпис методу розширення всередині інтерфейсу ...
Пітер Csala

1
@ Peter Csala: Вибачте, якщо це незрозуміло. Я оновив пост, щоб зробити його більш зрозумілим, і перейменував SomeNotAnExtensionMethod на SomeRegularMethod.
pasx

Як передати iRealпараметр SomeExtensionMethodу випадку ITest? Якщо ви передаєте це як перший параметр, то як зробити налаштування? Setup( x=> x.SomeExtensionMethod(x, ...)це призведе до виключення з виконання.
Пітер Цала

Ви не переходите. З точки зору макету, ITest містить звичайний метод без цього параметра для відповідності підпису викликів методу розширення у вашому коді, тому ви ніколи не отримаєте тут IReal, і в будь-якому випадку реалізація IReal / ITest - сама макет . Якщо ви хочете отримати доступ до деяких властивостей знученого IReal в SomeExtensionMethod, ви повинні зробити все це в макеті, наприклад: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => ви можете доступ до _mockCache тут);)
pasx

8

Ви можете легко знущатися над методом розширення за допомогою JustMock . API такий же, як і глузування з звичайного методу. Розглянемо наступне

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

Щоб упорядкувати та перевірити цей метод, використовуйте наступне:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

Ось також посилання на документацію: Методи розширення глузування


2

Мені подобається використовувати обгортку (модель адаптера), коли я обгортаю сам предмет. Я не впевнений, що використовував би це для обгортання методу розширення, який не є частиною об'єкта.

Я використовую внутрішню властивість Lazy Injectable будь-якого типу Action, Func, Predicate або delegate та дозволяю вводити (замінювати) метод під час одиничного тесту.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

Потім ви викликаєте Func замість фактичного методу.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

Для більш повного прикладу ознайомтеся з http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


Це добре, але читачі повинні знати, що _DoWorkMethod - це нове поле класу, якому кожен екземпляр класу має виділити ще одне поле. Це буває рідко, але це буває в залежності від кількості примірників, які ви виділяєте в будь-який час. Ви можете подолати цю проблему, зробивши _DoWorkMethod статичним. Мінус у тому, що якщо у вас одночасно працюють одиничні тести, два різні тести одиниці, які можуть змінювати одне і те ж статичне значення, завершаться.
zumalifeguard

-1

Отже, якщо ви використовуєте Moq і хочете знущатися над результатом методу розширення, тоді ви можете використовувати SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) в екземплярі класу mock, який має метод розширення, з якого ви намагаєтеся знущатися.

Він не ідеальний, але для цілей тестування одиниць він працює добре.


Я вважаю, що це SetReturnsDefault <T> ()
Девід

що ніколи не повертає конкретний екземпляр. Просто нуль у разі користувацького класу c #!
HelloWorld
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.