Moqを使用して単体テストの非同期メソッドをモックする


180

Web API呼び出しを行うサービスのメソッドをテストしています。HttpClientローカルでWebサービス(ソリューションの別のプロジェクトにある)も実行している場合は、単体テストで通常の方法を使用するとうまくいきます。

ただし、変更をチェックインすると、ビルドサーバーはWebサービスにアクセスできないため、テストは失敗します。

私は、ユニットテスト用にIHttpClientインターフェイスを作成し、アプリケーションで使用するバージョンを実装することで、これを回避する方法を考案しました。単体テストでは、モック化された非同期postメソッドを使用して、モック化されたバージョンを完成させます。ここで問題が発生しました。HttpStatusResultこの特定のテストに対してOKを返したいのですが。別の同様のテストでは、悪い結果が返されます。

テストは実行されますが、完了することはありません。待ちに待っています。私は非同期プログラミング、デリゲート、Moq自体に不慣れで、しばらくの間SOとグーグルを検索してきましたが、まだこの問題を乗り越えられないようです。

これが私がテストしようとしている方法です:

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 2013

2
Re:偽のIHttpClient、私はそれを検討しましたが、Web APIから返される予想される動作に基づいて、さまざまなテストに対してさまざまなHttpStatusCodesを返すことができるようにする必要がありました。
mvanella 2013

3
@mvanella:はい、あなたが望むものを何でも返すことができる偽物を作成します。考えるだけの何か。
Jon Skeet

134
これを今見つけた人のために、Moq 4.2にはと呼ばれる拡張機能がありReturnsAysnc、これはまさにこれを行います。
Stuart Grassie 14

3
@legacybass APIドキュメントでは、ほぼ1年前にリリースされた v4.2.1312.1622に対してビルドされていると記載されていますが、ドキュメントへのリンクが見つかりません。そのリリースの数日前に行われたこのコミットを参照してください。APIドキュメントが更新されない理由について...
Stuart Grassie 2014

17

上記の@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.