ฉันจะใช้ Moq เพื่อจำลองวิธีการขยายได้อย่างไร


87

ฉันกำลังเขียนการทดสอบที่ขึ้นอยู่กับผลลัพธ์ของวิธีการขยาย แต่ฉันไม่ต้องการให้ความล้มเหลวในอนาคตของวิธีการขยายนั้นทำลายการทดสอบนี้ การเยาะเย้ยผลลัพธ์นั้นดูเหมือนเป็นทางเลือกที่ชัดเจน แต่Moq ดูเหมือนจะไม่เสนอวิธีที่จะแทนที่วิธีการแบบคงที่ (ข้อกำหนดสำหรับวิธีการขยาย) มีความคิดที่คล้ายกันกับ Moq.Protected และ Moq.Stub แต่ดูเหมือนจะไม่เสนออะไรให้กับสถานการณ์นี้ ฉันพลาดอะไรไปหรือฉันควรจะไปทางอื่น

นี่เป็นตัวอย่างเล็ก ๆ น้อย ๆ ที่ล้มเหลวด้วยปกติ"ความคาดหวังที่ไม่ถูกต้องเกี่ยวกับการเป็นสมาชิกที่ไม่ใช่ overridable" นี่เป็นตัวอย่างที่ไม่ดีที่ต้องล้อเลียนวิธีการขยาย แต่ควรทำ

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

สำหรับผู้ที่ชื่นชอบ TypeMock ที่อาจแนะนำให้ฉันใช้ Isolator แทน: ฉันขอขอบคุณในความพยายามนี้เนื่องจากดูเหมือนว่า TypeMock สามารถทำงานได้โดยปิดตาและทำให้มึนงง แต่งบประมาณของเราจะไม่เพิ่มขึ้นในเร็ว ๆ นี้


3
ซ้ำสามารถพบได้ที่นี่: stackoverflow.com/questions/2295960/...
Oliver

6
คำถามนี้เก่ากว่าคำถามนั้นทั้งปี หากมีการทำซ้ำก็จะไปอีกทาง
patridge

2
ยังไม่มีทางออกที่เหมาะสมในปี 2019!
TanvirArjel

1
@TanvirArjel ที่จริงจากหลายปีที่ผ่านมาคุณสามารถใช้JustMockเพื่อจำลองวิธีการขยายได้ และทำได้ง่ายเหมือนกับการเยาะเย้ยวิธีการอื่น ๆ นี่คือลิงค์ไปยังเอกสารประกอบ: Extension Methods Mocking
Mihail Vladov

คำตอบ:


71

วิธีการขยายเป็นเพียงวิธีการคงที่ในการปลอมตัว การจำลองเฟรมเวิร์กเช่น Moq หรือ Rhinomocks สามารถสร้างอินสแตนซ์จำลองของวัตถุเท่านั้นซึ่งหมายความว่าไม่สามารถเยาะเย้ยวิธีการคงที่


70
@ Mendelt .. แล้วหน่วยหนึ่งจะทดสอบวิธีการที่มีวิธีการขยายภายในอย่างไร? ทางเลือกที่เป็นไปได้คืออะไร?
Sai Avinash

2
@ อเล็กซานเดอร์ฉันมีคำถามเดียวกันแล้วสิ่งนี้ก็ตอบได้อย่างน่าอัศจรรย์: agooddayforscience.blogspot.com/2017/08/… - ลองดูสิมันช่วยชีวิต!
Letie

31

หากคุณสามารถเปลี่ยนโค้ดวิธีการส่วนขยายได้คุณสามารถโค้ดแบบนี้เพื่อทดสอบ:

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!";
    }
}

ดังนั้นวิธีการขยายจึงเป็นเพียงเครื่องห่อหุ้มรอบ ๆ อินเทอร์เฟซการใช้งาน

(คุณสามารถใช้เพียงคลาสการนำไปใช้งานโดยไม่มีวิธีการขยายซึ่งเป็นประเภทของน้ำตาลในการสังเคราะห์)

และคุณสามารถจำลองอินเทอร์เฟซการใช้งานและตั้งเป็นการใช้งานสำหรับคลาสส่วนขยาย

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

16

ฉันรู้ว่าคำถามนี้ไม่ได้ใช้งานมาประมาณหนึ่งปีแล้ว แต่ Microsoft ได้เปิดตัวเฟรมเวิร์กเพื่อจัดการสิ่งนี้ที่เรียกว่า Molesไฝ

นี่คือบทเรียนบางส่วนเช่นกัน:

  • DimeCasts.net
  • บทแนะนำของ Nikolai Tillman


  • 15

    ฉันสร้างคลาส Wrapper สำหรับวิธีการขยายที่ฉันต้องการในการเยาะเย้ย

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

    จากนั้นคุณสามารถจำลองวิธีการห่อในการทดสอบและโค้ดของคุณด้วยคอนเทนเนอร์ IOC ของคุณ


    นี่เป็นวิธีแก้ปัญหาที่คุณไม่สามารถใช้ไวยากรณ์วิธีการส่วนขยายในโค้ดของคุณได้อีกต่อไป แต่จะช่วยเมื่อคุณไม่สามารถเปลี่ยนคลาสเมธอดส่วนขยายได้
    ข้อมูล

    @informatorius คุณหมายถึงอะไร? ในโค้ดของคุณคุณใช้ MyExtension () จากคลาส MyExtensions ในการทดสอบของคุณคุณใช้ MyExtension () จากคลาส ExtensionMethodsWrapper () ที่ให้พารามิเตอร์
    ranthonissen

    หากชั้นเรียนของฉันที่อยู่ระหว่างการทดสอบใช้ MyExtension () จาก MyExtensions ฉันจะไม่สามารถล้อเลียน MyExtensions ได้ ดังนั้นคลาสที่ทดสอบต้องใช้ IExtensionMethodsWrapper จึงจะสามารถล้อเลียนได้ แต่คลาสภายใต้การทดสอบไม่สามารถใช้ไวยากรณ์วิธีการขยายได้อีกต่อไป
    ข้อมูล

    1
    นั่นคือจุดรวมของ OP นี่เป็นวิธีแก้ปัญหาสำหรับสิ่งนั้น
    ranthonissen

    4

    สำหรับวิธีการขยายโดยปกติฉันใช้แนวทางต่อไปนี้:

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

    ช่วยให้การฉีด _doSumm ค่อนข้างง่าย


    2
    ฉันเพิ่งลองสิ่งนี้ แต่มีปัญหาเมื่อส่งผ่านวัตถุล้อเลียนผู้ปกครองที่ส่วนขยายมีไว้สำหรับเมื่อส่งต่อไปยังวิธีการอื่นซึ่งอยู่ในแอสเซมบลีอื่น ในกรณีนั้นแม้จะใช้เทคนิคนี้ แต่ส่วนขยายดั้งเดิมจะถูกเลือกเมื่อเมธอดถูกเรียกใช้ในแอสเซมบลีอื่นปัญหาการกำหนดขอบเขต ถ้าฉันโทรโดยตรงในโครงการทดสอบหน่วยก็ใช้ได้ แต่เมื่อคุณทดสอบโค้ดอื่นที่เรียกวิธีการขยายมันก็ไม่ได้ช่วยอะไร
    Stephen York

    @ สตีเฟ่นไม่แน่ใจว่าจะหลีกเลี่ยงสิ่งนี้ได้อย่างไร ฉันไม่เคยมีปัญหาการกำหนดขอบเขตแบบนี้มาก่อน
    dmigo

    0

    สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือจัดเตรียมการใช้งานแบบกำหนดเองสำหรับประเภทที่มีวิธีการขยายเช่น:

    [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;
        }
    }
    
    โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
    Licensed under cc by-sa 3.0 with attribution required.