ขั้นต่ำ: วิธีการที่จะได้รับพารามิเตอร์ที่ส่งผ่านไปยังวิธีการของการบริการเยาะเย้ย


170

ลองนึกภาพชั้นเรียนนี้

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

โม (c) จัดการ cking Handler ในการทดสอบของ Foo ฉันจะสามารถตรวจสอบสิ่งที่Bar()ผ่านไปได้_h.AsyncHandleอย่างไร


คุณหมายถึง "AsyncHandle" (extra "n") หรือไม่ และคุณสามารถโพสต์รหัสสำหรับ Handler หรือระบุชื่อประเภทที่ผ่านการรับรองว่าเป็นประเภทมาตรฐานได้หรือไม่
TrueWill

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

1
ไม่มีทั้ง Foo และ Bar () หรืออะไรอย่างนี้ มันเป็นเพียงโค้ดตัวอย่างที่จะแสดงสถานการณ์ที่ฉันอยู่โดยไม่ทำให้ไขว้เขวจากข้อมูลเฉพาะของแอปพลิเคชัน และฉันก็ได้คำตอบมาฉันหวังว่าจะได้
Jan

คำตอบ:


284

คุณสามารถใช้ Mock.Callback-method:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

หากคุณต้องการตรวจสอบสิ่งที่เรียบง่ายบนอาร์กิวเมนต์ที่ส่งผ่านคุณสามารถทำได้โดยตรง:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));

36
หมายเหตุด้านข้างหากคุณมีหลายอาร์กิวเมนต์ในฟังก์ชั่นของคุณคุณจะต้องระบุประเภททั้งหมดในCallback<>()เมธอด Moq ทั่วไป ตัวอย่างเช่นถ้าคุณมีวิธีการนิยามคุณจะต้องHandler.AnsyncHandle(string, SomeResponse) /* ... */.Callback<string, SomeResponse>(r => result = r);ฉันไม่พบสิ่งนี้ที่ระบุไว้อย่างชัดเจนในหลายแห่งดังนั้นฉันจึงคิดว่าฉันจะเพิ่มที่นี่
Frank Bryce

12
แค่ต้องการแก้ไข @Frank ในกรณีที่คุณไม่เห็นคำตอบ @JavaJudt วิธีการรับสองอาร์กิวเมนต์อย่างถูกต้องคือ:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp

2
นอกจากนี้คุณยังสามารถบรรลุเป้าหมายเดียวกันได้โดยเพียงแค่ประกาศประเภทของการขัดแย้งในการแสดงออกแลมบ์ดาดังนี้:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637

1
คุณช่วยปรับปรุงการตอบสนองของคุณให้มีการอ้างอิงถึงCapture.Inผู้ช่วยในตัวได้หรือไม่
cao

29

คำตอบของ Gamlor เหมาะสำหรับฉัน แต่ฉันคิดว่าฉันจะขยายความคิดเห็นของ John Carpenter เพราะฉันกำลังมองหาวิธีแก้ปัญหาที่เกี่ยวข้องกับพารามิเตอร์มากกว่าหนึ่งตัว ฉันคิดว่าคนอื่น ๆ ที่สะดุดกับหน้านี้อาจอยู่ในสถานการณ์ที่คล้ายคลึงกัน ผมพบว่าข้อมูลนี้ในเอกสารขั้นต่ำ

ฉันจะใช้ตัวอย่างของ Gamlor แต่ลองทำเป็นวิธี AsyncHandle รับสองอาร์กิวเมนต์: a stringและSomeResponseวัตถุ

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

โดยทั่วไปคุณเพียงแค่เพิ่มIt.IsAny<>()ประเภทที่เหมาะสมเพิ่มประเภทอื่นลงในCallbackเมธอดและเปลี่ยนนิพจน์แลมบ์ดาตามความเหมาะสม


23

วิธีการโทรกลับจะทำงานได้อย่างแน่นอน แต่ถ้าคุณกำลังทำสิ่งนี้กับวิธีที่มีพารามิเตอร์จำนวนมาก นี่คือสิ่งที่ฉันเคยใช้ในการลบแผ่นบาง ๆ

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

นี่คือแหล่งที่มาสำหรับ ArgumentCaptor:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}

21

คำตอบของ Gamlor ทำงานได้ แต่อีกวิธีหนึ่งในการทำมัน (และอีกวิธีหนึ่งที่ฉันคิดว่าแสดงออกได้มากกว่าในการทดสอบ) คือ ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

ตรวจสอบว่ามีประสิทธิภาพมากและควรใช้เวลาในการทำความคุ้นเคย


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

1
ฉันต้องการเก็บค่าส่งผ่านเนื่องจากฉันต้องตรวจสอบว่าชุดของวัตถุทั้งหมดที่ผ่านไปแล้ว
MrFox

นอกจากนี้บางครั้งพารามิเตอร์เป็นเอนทิตีและคุณต้องการยืนยันแยกต่างหากสำหรับแต่ละฟิลด์ในเอนทิตี วิธีที่ดีที่สุดในการทำเช่นนั้นคือการจับพารามิเตอร์แทนที่จะใช้ตรวจสอบและจับคู่
เควินหว่อง

13

ทางเลือกคือยังใช้คุณลักษณะของCapture.In moqมันเป็นmoqคุณสมบัติOOTB ที่เปิดใช้งานการจับอาร์กิวเมนต์ในคอลเลกชัน

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()

1
คำตอบที่ดี! ฉันไม่รู้จักคลาส Moq.Capture และ Moq.CaptureMatch จนกว่าจะเห็นคำตอบนี้ การจับภาพเป็นทางเลือกที่ดีกว่าสำหรับCallbackIMO เนื่องจากคุณใช้การจับภาพโดยตรงในรายการพารามิเตอร์มันมีแนวโน้มที่จะเกิดปัญหาน้อยกว่าเมื่อทำการปรับโครงสร้างรายการของวิธีการใหม่ดังนั้นจึงทำให้การทดสอบมีความเปราะน้อยลง ด้วยการติดต่อกลับคุณจะต้องรักษาพารามิเตอร์ที่ส่งผ่านในการตั้งค่าให้สอดคล้องกับพารามิเตอร์ประเภทที่ใช้สำหรับการโทรกลับและทำให้ฉันมีปัญหาในอดีต
Justin Holzer

7

คุณสามารถใช้It.Is<TValue>()matcher

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));

2

สิ่งนี้ยังใช้งานได้:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;

2

มีคำตอบที่ดีมากมายที่นี่! ไปพร้อมกับชุดคุณสมบัติ Moq นอกกรอบจนกว่าคุณจะต้องยืนยันเกี่ยวกับพารามิเตอร์คลาสต่างๆที่ส่งไปยังการอ้างอิงของคุณ หากคุณอยู่ในสถานการณ์เช่นนั้นคุณสมบัติ Moq Verify กับ It.Is matchers ทำงานได้ไม่ดีในการแยกความล้มเหลวในการทดสอบและวิธีการส่งคืน / โทรกลับในการจับอาร์กิวเมนต์เพิ่มบรรทัดของรหัสที่ไม่จำเป็นในการทดสอบของคุณ (และ การทดสอบที่ยาวนานเป็นสิ่งที่ไม่ต้องทำสำหรับฉัน)

นี่คือส่วนสำคัญ: https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6bพร้อมส่วนขยาย Moq (4.12) ที่ฉันเขียนซึ่งให้วิธีการที่ชัดเจนมากขึ้นในการยืนยันเกี่ยวกับข้อโต้แย้งที่ส่งผ่านไปยัง mocks โดยไม่มีข้อเสียดังกล่าวข้างต้น นี่คือลักษณะที่ตรวจสอบในตอนนี้:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

ฉันจะถูก stoked ถ้า Moq ให้คุณสมบัติที่ทำสิ่งเดียวกันในขณะที่เป็นการประกาศและการแยกความล้มเหลวนี้ ไขว้นิ้วเอาไว้!


1
ฉันชอบสิ่งนี้. Verify of Moq แข่งขันกับ Assert of xUnit สำหรับสิทธิ์ในการยืนยัน ไม่รู้สึกว่าถูกต้องในส่วน Moq ของการตั้งค่า การตั้งค่าคุณสมบัติ It.Is ควรรองรับการแสดงออกที่ไม่เหมือนกัน
โทมัส

"Moq แข่งขันกับ Assert of xUnit สำหรับอำนาจในการแสดง" - @ Thomas กล่าว ฉันจะเพิ่มว่ามันจะเสียการแข่งขัน Moq ทำได้ดีในการแจ้งให้คุณทราบว่ามีการโทรที่ตรงกันหรือไม่การยืนยันเฉพาะจะให้ข้อมูลที่ดีกว่า ข้อเสียเปรียบหลักสำหรับแนวทางของฉันคือการสูญเสียความปลอดภัยของประเภทและการตรวจสอบลำดับพารามิเตอร์ ฉันกำลังมองหาการปรับปรุงเรื่องนี้อยู่พักหนึ่งหวังว่าจะมีนินจา C # ที่สามารถแฮ็คตัวอย่างด้วยกันได้! มิฉะนั้นถ้าฉันหาวิธีที่ฉันจะอัปเดตนี้
Jacob McKay
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.