Bagaimana cara menggunakan Moq untuk meniru metode ekstensi?

87

Saya menulis tes yang bergantung pada hasil metode ekstensi tetapi saya tidak ingin kegagalan metode ekstensi itu di masa mendatang untuk merusak tes ini. Mengolok-olok hasil itu tampaknya merupakan pilihan yang jelas tetapi Moq tampaknya tidak menawarkan cara untuk menimpa metode statis (persyaratan untuk metode ekstensi). Ada ide serupa dengan Moq.Protected dan Moq.Stub, tetapi mereka tampaknya tidak menawarkan apa pun untuk skenario ini. Apakah saya melewatkan sesuatu atau haruskah saya melakukannya dengan cara yang berbeda?

Berikut adalah contoh sepele yang gagal dengan "Harapan tidak valid pada anggota yang tidak dapat diganti" yang biasa . Ini adalah contoh buruk dari perlunya mengejek metode ekstensi, tetapi seharusnya begitu.

public class SomeType {
    int Id { get; set; }
}

var ListMock = new Mock<List<SomeType>>();
ListMock.Expect(l => l.FirstOrDefault(st => st.Id == 5))
        .Returns(new SomeType { Id = 5 });

Adapun setiap pecandu TypeMock yang mungkin menyarankan saya menggunakan Isolator sebagai gantinya: Saya menghargai upaya karena sepertinya TypeMock dapat melakukan pekerjaan itu dengan mata tertutup dan mabuk, tetapi anggaran kami tidak meningkat dalam waktu dekat.

patridge
sumber
3
Duplikatnya dapat ditemukan di sini: stackoverflow.com/questions/2295960/… .
Oliver
6
Pertanyaan ini satu tahun lebih tua dari pertanyaan itu. Jika terjadi duplikasi, maka sebaliknya.
patridge
2
Masih belum ada solusi yang tepat di tahun 2019!
TanvirArjel
1
@TanvirArjel Sebenarnya, selama bertahun-tahun Anda dapat menggunakan JustMock untuk meniru metode ekstensi. Dan sesederhana mengejek metode lain. Berikut ini tautan ke dokumentasi: Metode Ekstensi
Mengolok-

Jawaban:

71

Metode ekstensi hanyalah metode statis yang disamarkan. Kerangka kerja tiruan seperti Moq atau Rhinomocks hanya dapat membuat contoh objek tiruan, ini berarti metode statis tiruan tidak dimungkinkan.

Mendelt
sumber
69
@ Mendelt..Lalu bagaimana satu unit menguji metode yang secara internal memiliki metode ekstensi? apa alternatif yang mungkin?
Sai Avinash
1
@Alexander Saya memiliki pertanyaan yang sama dan kemudian menjawabnya dengan fantastis: agooddayforscience.blogspot.com/2017/08/… -Lihat ini, ini adalah penyelamat hidup!
letie
31

Jika Anda dapat mengubah kode metode ekstensi maka Anda dapat mengkodekannya seperti ini untuk dapat menguji:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

public static class MyExtensions
{
    public static IMyImplementation Implementation = new MyImplementation();

    public static string MyMethod(this object obj)
    {
        return Implementation.MyMethod(obj);
    }
}

public interface IMyImplementation
{
    string MyMethod(object obj);
}

public class MyImplementation : IMyImplementation
{
    public string MyMethod(object obj)
    {
        return "Hello World!";
    }
}

Jadi metode ekstensi hanyalah pembungkus di sekitar antarmuka implementasi.

(Anda dapat menggunakan hanya kelas implementasi tanpa metode ekstensi yang semacam gula sintaksis.)

Dan Anda dapat membuat tiruan antarmuka implementasi dan menetapkannya sebagai implementasi untuk kelas ekstensi.

public class MyClassUsingExtensions
{
    public string ReturnStringForObject(object obj)
    {
        return obj.MyMethod();
    }
}

[TestClass]
public class MyTests
{
    [TestMethod]
    public void MyTest()
    {
        // Given:
        //-------
        var mockMyImplementation = new Mock<IMyImplementation>();

        MyExtensions.Implementation = mockMyImplementation.Object;

        var myClassUsingExtensions = new MyClassUsingExtensions();

        // When:
        //-------
        var myObject = new Object();
        myClassUsingExtensions.ReturnStringForObject(myObject);

        //Then:
        //-------
        // This would fail because you cannot test for the extension method
        //mockMyImplementation.Verify(m => m.MyMethod());

        // This is success because you test for the mocked implementation interface
        mockMyImplementation.Verify(m => m.MyMethod(myObject));
    }
}
informatorius
sumber
16

Saya tahu pertanyaan ini belum aktif selama sekitar satu tahun tetapi Microsoft merilis kerangka kerja untuk menangani persis ini yang disebut Moles .

Berikut beberapa tutorialnya juga:

  • DimeCasts.net
  • Tutorial Nikolai Tillman

  • Mike Fielden
    sumber
    15

    Saya membuat kelas pembungkus untuk metode ekstensi yang perlu saya tiru.

    public static class MyExtensions
    {
        public static string MyExtension<T>(this T obj)
        {
            return "Hello World!";
        }
    }
    
    public interface IExtensionMethodsWrapper
    {
        string MyExtension<T>(T myObj);
    }
    
    public class ExtensionMethodsWrapper : IExtensionMethodsWrapper
    {
        public string MyExtension<T>(T myObj)
        {
            return myObj.MyExtension();
        }
    }
    

    Kemudian Anda dapat memalsukan metode pembungkus dalam pengujian dan kode dengan penampung IOC Anda.

    ranthonissen.dll
    sumber
    Ini adalah solusi di mana Anda tidak dapat menggunakan sintaks metode ekstensi lagi dalam kode Anda. Tetapi itu membantu ketika Anda tidak dapat mengubah kelas metode ekstensi.
    informatorius
    @informatorius Apa yang Anda maksud dengan itu? Dalam kode Anda, Anda menggunakan MyExtension () dari kelas MyExtensions. Dalam pengujian Anda, Anda menggunakan MyExtension () dari kelas ExtensionMethodsWrapper () yang menyediakan parameter.
    ranthonissen
    Jika kelas saya yang diuji menggunakan MyExtension () dari MyExtensions maka saya tidak dapat memalsukan MyExtensions. Jadi kelas yang diuji harus menggunakan IExtensionMethodsWrapper sehingga dapat diolok-olok. Tapi kemudian kelas yang diuji tidak dapat menggunakan sintaks metode ekstensi lagi.
    informatorius
    1
    Itulah inti dari OP. Ini adalah solusi untuk itu.
    ranthonissen
    4

    Untuk metode penyuluhan saya biasanya menggunakan pendekatan berikut:

    public static class MyExtensions
    {
        public static Func<int,int, int> _doSumm = (x, y) => x + y;
    
        public static int Summ(this int x, int y)
        {
            return _doSumm(x, y);
        }
    }
    

    Ini memungkinkan untuk menyuntikkan _doSumm dengan cukup mudah.

    dmigo
    sumber
    2
    Saya baru saja mencoba ini tetapi ada masalah ketika melewati objek Parent mocked yang ekstensi itu untuk saat meneruskannya ke beberapa metode lain yang ada di assembly lain. Dalam kasus itu bahkan dengan teknik ini ekstensi asli diambil ketika metode tersebut dipanggil dalam rakitan lain, semacam masalah pelingkupan. Jika saya menelepon langsung dalam proyek Unit Test, tidak apa-apa, tetapi ketika Anda menguji kode lain yang memanggil metode ekstensi, itu tidak membantu.
    Stephen York
    @ Stephen Tidak yakin bagaimana menghindari ini. Saya tidak pernah mengalami masalah pelingkupan seperti ini
    dmigo
    0

    Hal terbaik yang dapat Anda lakukan adalah menyediakan implementasi kustom untuk jenis yang memiliki metode ekstensi, misalnya:

    [Fact]
    public class Tests
    {
        public void ShouldRunOk()
        {
            var service = new MyService(new FakeWebHostEnvironment());
    
            // Service.DoStuff() internally calls the SomeExtensionFunction() on IWebHostEnvironment
            // Here it works just fine as we provide a custom implementation of that interface
            service.DoStuff().Should().NotBeNull();
        }
    }
    
    public class FakeWebHostEnvironment : IWebHostEnvironment
    {
        /* IWebHostEnvironment implementation */
    
        public bool SomeExtensionFunction()
        {
            return false;
        }
    }
    
    Ross
    sumber