Mengejek Metode Ekstensi dengan Moq

175

Saya memiliki Antarmuka yang sudah ada sebelumnya ...

public interface ISomeInterface
{
    void SomeMethod();
}

dan saya telah memperpanjang pengenalan ini menggunakan mixin ...

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

Saya memiliki kelas yang memanggil ini yang ingin saya uji ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

dan tes di mana saya ingin mengejek antarmuka dan memverifikasi panggilan ke metode ekstensi ...

    [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();
    }

Namun menjalankan tes ini menghasilkan pengecualian ...

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

Pertanyaan saya adalah, adakah cara yang bagus untuk mengejek panggilan mixin?

Russell Giddings
sumber
3
Dalam pengalaman saya, istilah mixin dan metode ekstensi adalah hal yang terpisah. Saya akan menggunakan yang terakhir dalam contoh ini untuk menghindari mixups: P
Ruben Bartelink
3
Duplikat dari: stackoverflow.com/questions/562129/… .
Oliver

Jawaban:

33

Anda tidak dapat "langsung" menggunakan metode statis (karenanya metode ekstensi) dengan kerangka kerja mengejek. Anda dapat mencoba Moles ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ), alat gratis dari Microsoft yang menerapkan pendekatan berbeda. Berikut ini deskripsi alatnya:

Tahi lalat adalah kerangka kerja ringan untuk rintisan pengujian dan jalan memutar di .NET yang didasarkan pada delegasi.

Tahi lalat dapat digunakan untuk memutar metode .NET, termasuk metode non-virtual / statis dalam tipe tertutup.

Anda dapat menggunakan Mol dengan kerangka pengujian apa pun (independen tentang hal itu).

Daniele Armanasco
sumber
2
Selain Moles, ada kerangka kerja mengejek (tidak bebas) lain yang menggunakan API profiler .NET untuk mengolok-olok objek dan dengan demikian dapat menggantikan panggilan apa pun. Dua yang saya tahu adalah JustMock dan TypeMock Isolator milik Telerik .
Marcel Gosselin
6
Tahi lalat dalam teori itu baik, tetapi saya menemukan tiga masalah ketika saya melakukan percobaan yang menghentikan saya menggunakannya ... 1) Itu tidak berjalan di Resharper NUnit runner 2) Anda harus secara manual membuat rakitan mol untuk setiap rakitan yang bertubuh 3 ) Anda harus membuat ulang rakitan mol secara manual setiap kali metode yang terhenti berubah.
Russell Giddings
26

Saya telah menggunakan Wrapper untuk mengatasi masalah ini. Buat objek pembungkus dan lulus metode mengejek Anda.

Lihat Mengejutkan Metode Statis untuk Pengujian Unit oleh Paul Irwin, ia memiliki contoh yang bagus.

Alvis
sumber
12
Saya suka jawaban ini karena apa yang dikatakannya (tanpa langsung mengatakannya) adalah Anda perlu mengubah kode Anda agar dapat diuji. Begitulah cara kerjanya. Memahami bahwa dalam desain microchip / IC / ASIC, chip tersebut tidak hanya harus dirancang untuk bekerja, tetapi dirancang lebih jauh lagi agar dapat diuji, karena jika Anda tidak dapat menguji microchip, itu tidak berguna - Anda tidak dapat menjamin itu akan kerja. Hal yang sama berlaku untuk perangkat lunak. Jika Anda belum membuatnya untuk diuji, itu ... tidak berguna. Bangun untuk diuji, yang dalam beberapa kasus berarti menulis ulang kode (dan menggunakan pembungkus), lalu buat tes otomatis yang mengujinya.
Michael Plautz
2
Saya membuat perpustakaan kecil yang membungkus Dapper, Dapper.Contrib, dan IDbConnection. github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido
15

Saya menemukan bahwa saya harus menemukan bagian dalam metode ekstensi yang saya coba tiru input, dan mengejek apa yang terjadi di dalam ekstensi.

Saya melihat menggunakan ekstensi sebagai menambahkan kode langsung ke metode Anda. Ini berarti saya perlu mengejek apa yang terjadi di dalam ekstensi daripada ekstensi itu sendiri.

chris31389
sumber
11

Anda dapat mengejek antarmuka uji yang mewarisi dari yang asli dan memiliki anggota dengan tanda tangan yang sama dengan metode ekstensi.

Anda kemudian dapat mengejek antarmuka uji, menambahkan yang asli ke tiruan dan memanggil metode pengujian dalam pengaturan.

Implementasi mock Anda kemudian dapat memanggil metode apa pun yang Anda inginkan atau cukup memeriksa bahwa metode itu disebut:

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

Berkat solusi Håvard S di pos ini untuk cara mengimplementasikan tiruan yang mendukung dua antarmuka. Setelah saya menemukannya, mengadaptasinya dengan antarmuka uji dan metode statis adalah cake walk.

pasx
sumber
Bisakah Anda menunjukkan kepada kami tolong tanda tangan sampel yang sama untuk SomeNotAnExtensionMethoddan SomeNotAnExtensionMethod? Sekarang saya tahu cara membuat tanda tangan metode ekstensi di dalam antarmuka ...
Peter Csala
1
@ Peter Csala: Maaf jika ini tidak jelas. Saya memperbarui posting untuk membuatnya lebih jelas dan berganti nama SomeNotAnExtensionMethod ke SomeRegularMethod.
pasx
Bagaimana cara Anda mengirim iRealparameter ke SomeExtensionMethoddalam kasus ITest? Jika Anda meneruskannya sebagai parameter pertama, lalu bagaimana cara Anda men-setup? Setup( x=> x.SomeExtensionMethod(x, ...)ini akan menyebabkan pengecualian runtime.
Peter Csala
Kamu tidak lulus itu. Dari sudut pandang tiruan ITest berisi metode biasa tanpa parameter ini untuk mencocokkan tanda tangan panggilan dengan metode ekstensi dalam kode Anda sehingga Anda tidak akan pernah menerima IReal di sini dan dalam hal apa pun penerapan IReal / ITest adalah tiruan itu sendiri . Jika Anda ingin mengakses beberapa properti dari IReal yang diolok-olok di SomeExtensionMethod, Anda harus melakukan semua itu di tiruannya misalnya: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => Anda dapat akses _mockCache di sini);)
pasx
8

Anda dapat dengan mudah mengejek metode ekstensi dengan JustMock . API sama dengan mengejek metode normal. Pertimbangkan yang berikut ini

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

Untuk mengatur dan memverifikasi metode ini gunakan yang berikut:

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);

Berikut ini juga tautan ke dokumentasi: Metode Ekstensi Mengejek

Mihail Vladov
sumber
2

Saya suka menggunakan pembungkus (pola adaptor) ketika saya membungkus objek itu sendiri. Saya tidak yakin saya akan menggunakannya untuk membungkus metode ekstensi, yang bukan bagian dari objek.

Saya menggunakan Properti Injectable Malas internal baik tipe Action, Func, Predicate, atau delegate dan memungkinkan untuk menyuntikkan (menukar) metode selama unit test.

    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;

Kemudian Anda memanggil Fungsi bukan metode yang sebenarnya.

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

Untuk contoh yang lebih lengkap, lihat http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/

Rhyous
sumber
Ini bagus, tetapi pembaca harus menyadari bahwa _DoWorkMethod adalah bidang baru dari kelas yang setiap instance kelas sekarang harus mengalokasikan satu bidang lagi. Ini jarang terjadi, tetapi kadang-kadang tergantung pada jumlah contoh yang Anda alokasikan pada satu waktu. Anda dapat mengatasi masalah ini dengan membuat _DoWorkMethod statis. Kelemahannya adalah bahwa jika Anda menjalankan unit test secara bersamaan, dua unit test yang berbeda yang dapat memodifikasi nilai statis yang sama akan selesai.
zumalifeguard
-1

Jadi jika Anda menggunakan Moq, dan ingin mengejek hasil dari metode Extension, maka Anda dapat menggunakannya SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn()) instance dari kelas mock yang memiliki metode ekstensi yang Anda coba tiru.

Ini tidak sempurna, tetapi untuk tujuan pengujian unit ini bekerja dengan baik.

ckernel
sumber
Saya percaya itu SetReturnsDefault <T> ()
David
itu tidak pernah mengembalikan contoh konkret. Hanya null jika ada kelas c # kustom!
HelloWorld