Moq를 사용하여 단위 테스트를위한 비동기 메소드 조롱


179

API호출 을 수행하는 서비스에 대한 방법을 테스트하고 있습니다. HttpClient웹 서비스 (솔루션의 다른 프로젝트에 위치)를 로컬로 실행하는 경우 일반을 사용하면 단위 테스트에 적합합니다.

그러나 변경 사항을 체크인하면 빌드 서버가 웹 서비스에 액세스 할 수 없으므로 테스트가 실패합니다.

IHttpClient인터페이스 를 만들고 응용 프로그램에서 사용하는 버전을 구현하여 단위 테스트를 위해이 문제를 해결할 방법을 고안했습니다 . 단위 테스트의 경우 모의 버전을 모의 비동기 게시 방법으로 완성합니다. 여기에 문제가 생겼습니다. HttpStatusResult이 특정 테스트에 대해 OK를 반환하고 싶습니다 . 다른 유사한 테스트의 경우 나쁜 결과를 반환합니다.

테스트는 실행되지만 완료되지는 않습니다. 기다리고 있습니다. 나는 비동기 프로그래밍, 델리게이트 및 Moq 자체에 익숙하지 않고 새로운 것을 배우면서 잠시 동안 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.");
}

내가 뭘 잘못하고 있죠?

도와 주셔서 감사합니다.

답변:


350

작업을 생성하고 있지만 시작하지 않으므로 완료되지 않습니다. 그러나 작업을 시작하는 것이 아니라 사용으로 변경하면 Task.FromResult<TResult>이미 완료된 작업이 제공됩니다.

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

이 방법으로 실제 비동기 성을 테스트하지는 않습니다. 그렇게하려면 좀 더 Task<T>세밀한 방식으로 제어 할 수 있는 작업을 만들려면 약간 더 많은 작업을 수행해야 합니다. 다른 날.

IHttpClient모든 것을 조롱하는 대신 가짜를 사용하는 것도 고려할 수 있습니다. 실제로 필요한 빈도에 따라 다릅니다.


2
대단히 감사합니다. 잘 작동했습니다. 나는 내가 이해하지 못하는 것이 아마도 단순한 것이라고 생각했다.
mvanella

2
Re : 가짜 IHttpClient, 나는 그것을 고려했지만 웹 API에서 오는 예상되는 행동을 기반으로 다른 테스트에 대해 다른 HttpStatusCodes를 반환 할 수 있어야했고, 이것은 더 많은 통제력을 부여하는 것처럼 보였다.
mvanella

3
@ mvanella : 예, 원하는 것을 반환 할 수있는 가짜를 만들 것입니다. 생각할 것.
Jon Skeet

134
지금 이것을 찾는 사람이라면 Moq 4.2에는라는 확장자 ReturnsAysnc가 있습니다.
Stuart Grassie

3
@legacybass API 문서에서 거의 정확히 1 년 전에 릴리스 된 v4.2.1312.1622에 대해 빌드되었다고 말하더라도 문서에 대한 링크를 찾을 수 없습니다 . 그 릴리스 며칠 전에 이루어진 커밋 을 참조하십시오 . API 문서가 업데이트되지 않는 이유에 관해서는 ...
Stuart Grassie

16

위의 @Stuart 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.