เหตุใดฉันจึงได้รับข้อยกเว้นพร้อมข้อความ“ ตั้งค่าไม่ถูกต้องบนสมาชิกที่ไม่ใช่เสมือน (overridable ใน VB) …”


176

ฉันมีการทดสอบหน่วยที่ฉันต้องจำลองวิธีการที่ไม่เสมือนจริงที่ส่งกลับประเภทบูล

public class XmlCupboardAccess
{
    public bool IsDataEntityInXmlCupboard(string dataId,
                                          out string nameInCupboard,
                                          out string refTypeInCupboard,
                                          string nameTemplate = null)
    {
        return IsDataEntityInXmlCupboard(_theDb, dataId, out nameInCupboard, out refTypeInCupboard, nameTemplate);
    }
}

ดังนั้นฉันมีวัตถุจำลองของXmlCupboardAccessคลาสและฉันพยายามตั้งค่าจำลองสำหรับวิธีการนี้ในกรณีทดสอบของฉันที่แสดงด้านล่าง

[TestMethod]
Public void Test()
{
    private string temp1;
    private string temp2;
    private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
    _xmlCupboardAccess.Setup(x => x.IsDataEntityInXmlCupboard(It.IsAny<string>(), out temp1, out temp2, It.IsAny<string>())).Returns(false); 
    //exception is thrown by this line of code
}

แต่บรรทัดนี้จะมีข้อยกเว้น

Invalid setup on a non-virtual (overridable in VB) member: 
x => x.IsDataEntityInXmlCupboard(It.IsAny<String>(), .temp1, .temp2, 
It.IsAny<String>())

ข้อเสนอแนะวิธีการแก้ไขข้อยกเว้นนี้?


การทดสอบของคุณขึ้นอยู่กับXmlCupboardAccessอะไร?
Preston Guillot

9
ที่เรียบง่าย .. virtualคุณต้องทำเครื่องหมาย Moq ไม่สามารถจำลองประเภทคอนกรีตที่ไม่สามารถแทนที่ได้
Simon Whitehead

คำตอบ:


265

Moq ไม่สามารถจำลองวิธีการที่ไม่ใช่เสมือนและคลาสที่ปิดผนึก ขณะเรียกใช้การทดสอบโดยใช้วัตถุจำลองจริง ๆ แล้วจะสร้างประเภทพร็อกซีในหน่วยความจำซึ่งสืบทอดมาจาก "XmlCupboardAccess" ของคุณและแทนที่พฤติกรรมที่คุณได้ตั้งค่าในวิธี "การตั้งค่า" และอย่างที่คุณทราบใน C # คุณสามารถแทนที่บางสิ่งได้หากมันถูกทำเครื่องหมายเป็นเสมือนซึ่งไม่ใช่กรณีของ Java Java ถือว่าทุกวิธีที่ไม่คงที่จะเป็นเสมือนโดยค่าเริ่มต้น

อีกสิ่งหนึ่งที่ฉันเชื่อว่าคุณควรพิจารณาคือแนะนำอินเทอร์เฟซสำหรับ "CupboardAccess" ของคุณแล้วเริ่มเย้ยอินเทอร์เฟซแทน มันจะช่วยให้คุณ decouple รหัสของคุณและมีประโยชน์ในระยะยาว

สุดท้ายมีกรอบเช่น: TypeMockและJustMockซึ่งทำงานโดยตรงกับ IL และดังนั้นจึงสามารถเยาะเย้ยวิธีการที่ไม่ใช่เสมือน อย่างไรก็ตามทั้งสองเป็นผลิตภัณฑ์เชิงพาณิชย์


59
+1 จากข้อเท็จจริงที่ว่าคุณควรเลียนแบบอินเตอร์เฟสเท่านั้น คำถามนี้แก้ไขสิ่งที่ฉันพบเพราะฉันล้อเลียนชั้นเรียนโดยไม่ตั้งใจและไม่ใช่ส่วนต่อประสานพื้นฐาน
Paul Raff

1
สิ่งนี้ไม่เพียงช่วยแก้ปัญหาเท่านั้น แต่เป็นวิธีที่ดีในการใช้อินเทอร์เฟซสำหรับคลาสทั้งหมดของคุณที่ต้องการทดสอบ Moq บังคับให้คุณต้องพึ่งพาการผกผันที่ดีโดยที่กรอบการเยาะเย้ยอื่น ๆ ช่วยให้คุณได้รับหลักการนี้
Xipooo

มันจะถูกพิจารณาว่าเป็นการละเมิดหลักการนี้หรือไม่หากฉันมีการนำไปใช้งานปลอมเช่น FakePeopleRepository ของอินเทอร์เฟซของฉันเช่น IPeopleRepository และฉันจำลองการใช้งานปลอม ฉันคิดว่า IoC ยังคงถูกเก็บรักษาไว้เพราะในการตั้งค่าการทดสอบของฉันฉันต้องส่งวัตถุปลอมไปยังคลาสบริการของฉันซึ่งใช้อินเทอร์เฟซในตัวสร้าง
paz

1
@paz จุดทั้งการใช้งานขั้นต่ำคือการหลีกเลี่ยงการใช้งานปลอม ทีนี้ลองพิจารณาถึงการใช้งานปลอมหลายรูปแบบที่คุณจะต้องตรวจสอบเงื่อนไขขอบเขต ฯลฯ ตามทฤษฎีแล้วคุณสามารถจำลองการใช้งานปลอมได้ แต่ในทางปฏิบัติดูเหมือนจะเป็นกลิ่นรหัส
Amol

โปรดทราบว่าข้อผิดพลาดนี้อาจเกิดขึ้นจริงกับวิธีการขยายบนอินเตอร์เฟสซึ่งอาจทำให้เกิดความสับสน
Dan Pantry

34

สำหรับผู้ที่มีปัญหาเช่นเดียวกับฉันฉันตั้งใจพิมพ์ผิดประเภทการใช้งานแทนอินเทอร์เฟซเช่น

var mockFileBrowser = new Mock<FileBrowser>();

แทน

var mockFileBrowser = new Mock<IFileBrowser>();

5

โปรดดู เหตุใดคุณสมบัติที่ฉันต้องการจำลองต้องเป็นเสมือนจริง

คุณอาจต้องเขียนส่วนต่อประสาน wrapper หรือทำเครื่องหมายคุณสมบัติเป็นเสมือน / นามธรรมเป็น Moq สร้างคลาสพร็อกซีที่ใช้ในการสกัดกั้นการโทรและส่งคืนค่าที่คุณกำหนดเองที่คุณใส่ในการ.Returns(x)โทร


5

แทนการเยาะเย้ยชั้นคอนกรีตคุณควรเยาะเย้ยอินเตอร์เฟซชั้นที่ แยกส่วนต่อประสานจากคลาส XmlCupboardAccess

public interface IXmlCupboardAccess
{
    bool IsDataEntityInXmlCupboard(string dataId, out string nameInCupboard, out string refTypeInCupboard, string nameTemplate = null);
}

และแทนที่จะเป็น

private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();

เปลี่ยนไป

private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();

3

คุณจะได้รับข้อผิดพลาดนี้เช่นกันหากคุณตรวจสอบว่ามีการเรียกใช้ส่วนขยายของอินเทอร์เฟซ

ตัวอย่างเช่นถ้าคุณกำลังเยาะเย้ย:

var mockValidator = new Mock<IValidator<Foo>>();
mockValidator
  .Verify(validator => validator.ValidateAndThrow(foo, null));

คุณจะได้รับข้อยกเว้นเดียวกันเนื่องจาก.ValidateAndThrow()เป็นส่วนขยายบนIValidator<T>อินเทอร์เฟซ

public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance, string ruleSet = null)...


-12

รหัส:

private static void RegisterServices(IKernel kernel)
{
    Mock<IProductRepository> mock=new Mock<IProductRepository>();
    mock.Setup(x => x.Products).Returns(new List<Product>
    {
        new Product {Name = "Football", Price = 23},
        new Product {Name = "Surf board", Price = 179},
        new Product {Name = "Running shose", Price = 95}
    });

    kernel.Bind<IProductRepository>().ToConstant(mock.Object);
}        

แต่ดูข้อยกเว้น


4
คุณช่วยอธิบายวิธีแก้ปัญหาของคุณได้ไหม? นอกจากนี้ "ดูข้อยกเว้น ... " ก็ถูกแขวนไว้ คุณขยายได้ไหม
amadan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.