ใช้ Moq เพื่อจำลองวิธีอะซิงโครนัสสำหรับการทดสอบหน่วย


180

ฉันกำลังทดสอบวิธีการสำหรับบริการที่APIโทรผ่านเว็บ ใช้HttpClientงานปกติได้ดีสำหรับการทดสอบหน่วยถ้าฉันยังเรียกใช้บริการเว็บ (อยู่ในโครงการอื่นในการแก้ปัญหา) ในพื้นที่

อย่างไรก็ตามเมื่อฉันเช็คอินการเปลี่ยนแปลงของฉันเซิร์ฟเวอร์บิลด์จะไม่สามารถเข้าถึงบริการเว็บดังนั้นการทดสอบจะล้มเหลว

ฉันได้คิดค้นวิธีนี้สำหรับการทดสอบหน่วยของฉันโดยการสร้างIHttpClientอินเทอร์เฟซและใช้เวอร์ชันที่ฉันใช้ในแอปพลิเคชันของฉัน สำหรับการทดสอบหน่วยฉันสร้างเวอร์ชันที่เยาะเย้ยโดยใช้วิธีการโพสต์แบบอะซิงโครนัสที่เยาะเย้ย ที่นี่ฉันมีปัญหา ฉันต้องการส่งคืนตกลงHttpStatusResultสำหรับการทดสอบนี้โดยเฉพาะ สำหรับการทดสอบอื่นที่คล้ายคลึงกันฉันจะคืนผลลัพธ์ที่ไม่ดี

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

นี่คือวิธีที่ฉันพยายามทดสอบ:

public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
    // do stuff
    try
    {
        // The test hangs here, never returning
        HttpResponseMessage response = await client.PostAsync(uri, content);

        // more logic here
    }
    // more stuff
}

นี่คือวิธีทดสอบหน่วยของฉัน:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "bob@example.com",
        ToAddress = "bill@example.com",
        CCAddress = "brian@example.com",
        BCCAddress = "ben@example.com",
        Subject = "Hello",
        Body = "Hello World."
    };
    var mockClient = new Mock<IHttpClient>();
    mockClient.Setup(c => c.PostAsync(
        It.IsAny<Uri>(),
        It.IsAny<HttpContent>()
        )).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

    bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);

    Assert.IsTrue(result, "Queue failed.");
}

ผมทำอะไรผิดหรือเปล่า?

ขอขอบคุณสำหรับความช่วยเหลือของคุณ.

คำตอบ:


351

คุณกำลังสร้างงาน แต่ไม่เริ่มงานดังนั้นจึงไม่เสร็จ อย่างไรก็ตามไม่เพียงแค่เริ่มงาน แต่ให้เปลี่ยนเป็นการใช้Task.FromResult<TResult>ซึ่งจะให้งานที่เสร็จสมบูรณ์แล้ว:

...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

โปรดทราบว่าคุณจะไม่ทำการทดสอบแบบอะซิงโครนัสจริงด้วยวิธีนี้ - ถ้าคุณต้องการทำเช่นนั้นคุณต้องทำงานอีกเล็กน้อยเพื่อสร้างสิ่งTask<T>ที่คุณสามารถควบคุมได้ในลักษณะที่ละเอียดยิ่งขึ้น ... แต่นั่นเป็นสิ่งสำหรับ วันอื่น.

คุณอาจต้องการพิจารณาใช้ของปลอมIHttpClientแทนการเยาะเย้ยทุกอย่างขึ้นอยู่กับว่าคุณต้องการมันบ่อยแค่ไหน


2
ขอบคุณมาก. มันใช้งานได้ดีมาก ฉันคิดว่ามันอาจเป็นสิ่งที่เรียบง่ายที่ฉันไม่เข้าใจ
mvanella

2
Re: Fake IHttpClient ฉันคิดว่า แต่ฉันต้องสามารถกลับ HttpStatusCodes ที่แตกต่างกันสำหรับการทดสอบที่แตกต่างกันตามพฤติกรรมที่คาดหวังกลับมาจากเว็บ API และดูเหมือนจะทำให้ฉันควบคุมได้มากขึ้น
mvanella

3
@mvanella: ใช่ดังนั้นคุณต้องสร้างของปลอมซึ่งสามารถคืนสิ่งที่คุณต้องการ เพียงแค่บางสิ่งบางอย่างที่จะคิดเกี่ยวกับ.
Jon Skeet

134
สำหรับทุกคนที่พบสิ่งนี้ในตอนนี้ Moq 4.2 มีส่วนขยายที่เรียกว่าReturnsAysncซึ่งทำสิ่งนี้ทุกประการ
Stuart Grassie

3
@legacybass ฉันไม่สามารถหาลิงก์ไปยังเอกสารใด ๆ ได้ถึงแม้ว่าเอกสาร API จะบอกว่าพวกเขาสร้างขึ้นจาก v4.2.1312.1622 ซึ่งเปิดตัวเกือบปีที่แล้ว ดูการกระทำนี้ซึ่งทำไว้ก่อนหน้านั้นสองสามวัน สาเหตุที่เอกสาร API ไม่ได้รับการอัปเดต ...
Stuart Grassie

17

แนะนำคำตอบของ @Start Grassie ด้านบน

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });

1

ด้วยMock.Of<...>(...)สำหรับasyncวิธีการที่คุณสามารถใช้Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.