ฉันจะตรวจสอบได้อย่างไรว่าเมธอดถูกเรียกด้วย Moq เพียงครั้งเดียว


112

ฉันจะตรวจสอบได้อย่างไรว่าเมธอดถูกเรียกด้วย Moq เพียงครั้งเดียว Verify()เทียบกับVerifable()สิ่งที่เป็นจริงที่ทำให้เกิดความสับสน

คำตอบ:


165

คุณสามารถใช้Times.Once()หรือTimes.Exactly(1):

mockContext.Verify(x => x.SaveChanges(), Times.Once());
mockContext.Verify(x => x.SaveChanges(), Times.Exactly(1));

วิธีการในคลาสTimes มีดังนี้

  • AtLeast - ระบุว่าควรเรียกใช้เมธอดที่เยาะเย้ยเป็นครั้งเท่าต่ำสุด
  • AtLeastOnce - ระบุว่าควรเรียกใช้เมธอดจำลองหนึ่งครั้งเป็นอย่างต่ำ
  • AtMost - ระบุว่าควรเรียกใช้เมธอดที่เยาะเย้ยครั้งเป็นเวลาสูงสุด
  • AtMostOnce - ระบุว่าควรเรียกใช้เมธอดจำลองหนึ่งครั้งเป็นค่าสูงสุด
  • Between - ระบุว่าควรเรียกใช้เมธอดจำลองระหว่างจากถึงบางครั้ง
  • Exactly - ระบุว่าควรเรียกใช้เมธอดที่เยาะเย้ยครั้งละเท่า ๆ กัน
  • Never - ระบุว่าไม่ควรเรียกใช้วิธีการจำลอง
  • Once - ระบุว่าควรเรียกวิธีการจำลองแบบครั้งเดียว

เพียงจำไว้ว่าพวกเขาเป็นวิธีการเรียก; ฉันสะดุดขึ้นเรื่อย ๆ คิดว่ามันเป็นคุณสมบัติและลืมวงเล็บ


2
แล้วคุณจะรับ / ตั้งค่า mockContext ได้อย่างไร?
Choco

2
@ Choco ฉันคิดว่านั่นเป็นเพียงตัวอย่างจำลองของเขา ดังนั้นมันจึงเป็นสิ่งที่อยากvar mockContext = new Mock<IContext>()จะตั้งขึ้น
Zack Huber

ฉันเพียงแค่สงสัยว่าAtLeast, AtMost, BetweenหรือExactlyอาจจะมองว่าเป็นสถานที่ให้บริการ ฉันหมายความว่าพวกเขา obv ต้องการพารามิเตอร์เพื่อทำอะไรบางอย่าง
Danylo Yelizarov

8

ลองนึกภาพว่าเรากำลังสร้างเครื่องคิดเลขด้วยวิธีหนึ่งในการบวกจำนวนเต็ม 2 ตัว ลองจินตนาการต่อไปว่าความต้องการคือเมื่อเรียกวิธีการเพิ่มจะเรียกวิธีการพิมพ์ครั้งเดียว นี่คือวิธีที่เราจะทดสอบสิ่งนี้:

public interface IPrinter
{
    void Print(int answer);
}

public class ConsolePrinter : IPrinter
{
    public void Print(int answer)
    {
        Console.WriteLine("The answer is {0}.", answer);
    }
}

public class Calculator
{
    private IPrinter printer;
    public Calculator(IPrinter printer)
    {
        this.printer = printer;
    }

    public void Add(int num1, int num2)
    {
        printer.Print(num1 + num2);
    }
}

และนี่คือการทดสอบจริงพร้อมความคิดเห็นภายในรหัสเพื่อชี้แจงเพิ่มเติม:

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    public void WhenAddIsCalled__ItShouldCallPrint()
    {
        /* Arrange */
        var iPrinterMock = new Mock<IPrinter>();

        // Let's mock the method so when it is called, we handle it
        iPrinterMock.Setup(x => x.Print(It.IsAny<int>()));

        // Create the calculator and pass the mocked printer to it
        var calculator = new Calculator(iPrinterMock.Object);

        /* Act */
        calculator.Add(1, 1);

        /* Assert */
        // Let's make sure that the calculator's Add method called printer.Print. Here we are making sure it is called once but this is optional
        iPrinterMock.Verify(x => x.Print(It.IsAny<int>()), Times.Once);

        // Or we can be more specific and ensure that Print was called with the correct parameter.
        iPrinterMock.Verify(x => x.Print(3), Times.Once);
    }
}

หมายเหตุ : โดยค่าเริ่มต้น Moq จะทำลายคุณสมบัติและวิธีการทั้งหมดทันทีที่คุณสร้างวัตถุจำลอง แม้ว่าจะไม่มีการโทรSetupแต่ Moq ก็มีวิธีการต่างๆIPrinterเพื่อให้คุณสามารถโทรVerifyได้ อย่างไรก็ตามตามแนวปฏิบัติที่ดีฉันตั้งค่าไว้เสมอเนื่องจากเราอาจจำเป็นต้องบังคับใช้พารามิเตอร์กับวิธีการเพื่อให้เป็นไปตามความคาดหวังบางอย่างหรือค่าตอบแทนจากวิธีการเพื่อให้เป็นไปตามความคาดหวังบางอย่างหรือตามจำนวนครั้งที่มีการเรียกใช้


ฉันโทรVerifyไปTimes.Onceโดยที่ไม่เคยโทรSetupเลย แน่นอนฉันคาดหวังว่าVerifyจะระเบิดในกรณีนั้น แต่ก็ไม่เป็นเช่นนั้น
dudeNumber4

@ dudeNumber4 ไม่มันจะไม่ระเบิดเพราะโดยค่าเริ่มต้น Moq จะทำลายคุณสมบัติและวิธีการทั้งหมดทันทีที่คุณสร้างMockวัตถุ แม้ว่าจะไม่มีการโทรSetupแต่ Moq ก็มีวิธีการต่างๆIPrinterเพื่อให้คุณสามารถโทรVerifyได้ อย่างไรก็ตามตามแนวปฏิบัติที่ดีฉันมักจะตั้งค่าเนื่องจากเราอาจต้องบังคับใช้พารามิเตอร์กับวิธีการหรือค่าที่ส่งคืนจากวิธีการ
CodingYoshi

ขออภัยนั่นเป็นคำอธิบายที่แย่มาก ฉันโทรไปTimes.Exactly(1)และมันก็ไม่ล้มเหลวเมื่อเมธอดถูกเรียกสองครั้ง หลังจากเพิ่มSetupสำหรับวิธีที่เป็นปัญหาเท่านั้นที่ล้มเหลวอย่างถูกต้อง
dudeNumber4

2

ตัวควบคุมการทดสอบอาจเป็น:

  public HttpResponseMessage DeleteCars(HttpRequestMessage request, int id)
    {
        Car item = _service.Get(id);
        if (item == null)
        {
            return request.CreateResponse(HttpStatusCode.NotFound);
        }

        _service.Remove(id);
        return request.CreateResponse(HttpStatusCode.OK);
    }

และเมื่อเมธอด DeleteCars เรียกด้วย ID ที่ถูกต้องเราสามารถตรวจสอบได้ว่า Service remove method เรียกว่าหนึ่งครั้งโดยการทดสอบนี้:

 [TestMethod]
    public void Delete_WhenInvokedWithValidId_ShouldBeCalledRevomeOnce()
    {
        //arange
        const int carid = 10;
        var car = new Car() { Id = carid, Year = 2001, Model = "TTT", Make = "CAR 1", Price=2000 };
        mockCarService.Setup(x => x.Get(It.IsAny<int>())).Returns(car);

        var httpRequestMessage = new HttpRequestMessage();
        httpRequestMessage.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();

        //act
        var result = carController.DeleteCar(httpRequestMessage, vechileId);

        //assert
        mockCarService.Verify(x => x.Remove(carid), Times.Exactly(1));
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.