2013-03-26 43 views
32

私は404の例外を投げた場合のテストを記述しようとしています。このアクションNUnitの非同期テスト例外アサーション

// GET /blah 
public Task<User> Get(string domainUserName) 
{ 
     if (string.IsNullOrEmpty(domainUserName)) 
     { 
      throw new ArgumentException("No username specified."); 
     } 

     return Task.Factory.StartNew(
      () => 
       { 
        var user = userRepository.GetByUserName(domainUserName); 
        if (user != null) 
        { 
         return user; 
        } 

        throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, string.Format("{0} - username does not exist", domainUserName))); 
       }); 
} 

とコントローラUserControllerを持っています。ここで

は、私は、出力で、試してみましたが何である -

1)

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    Assert.That(async() => await userController.Get("foo"), Throws.InstanceOf<HttpResponseException>()); 
} 

結果 テスト

Expected: instance of <System.Web.Http.HttpResponseException> 
    But was: no exception thrown 

2)

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var httpResponseException = Assert.Throws<HttpResponseException>(() => userController.Get("foo").Wait()); 
    Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 
失敗しました

結果 試験

Expected: <System.Web.Http.HttpResponseException> 
    But was: <System.AggregateException> (One or more errors occurred.) 

3) テストが失敗

[Test] 
public void someTest() 
{ 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var httpResponseException = Assert.Throws<HttpResponseException>(async() => await userController.Get("foo")); 
    Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 

検索結果を失敗

Expected: <System.Web.Http.HttpResponseException> 
    But was: null 

4)

ExpectedExceptionがない場合、

  1. はなぜAssert.ThrowsがHttpResponseExceptionを処理しません -
    [Test] 
    [ExpectedException(typeof(HttpResponseException))] 
    public async void ShouldThrow404WhenNotFound() 
    {   var mockUserRepository = new Mock<IUserRepository>(); 
         mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    
        var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 
    
        var task = await userController.Get("foo"); 
    } 
    

    結果 テストは

    質問を渡しますか?

  2. 例外がスローされたことをテストするだけではいけません。私は応答のステータスコードを主張したいと思います。これを行う方法は何ですか?

これらの動作とその原因の比較は素晴らしいでしょう。

+0

あなたは、すべてのテストとして、あなたの公開タスク取得(文字列あるDomainUserName)からより多くのコードを追加する必要がありdomainUser =「foo」というためのもので、あなたが示した唯一の誤差があります空のdomainUser(またはnull)の場合 – JleruOHeP

+0

@ JleruOHeP - 感謝の念がちょっと持ち去られました。コードを編集しました。 –

+0

あなたの編集後も、すべてのテストケースは同じですか?そしてケース1はまだ例外をスローしませんか? – JleruOHeP

答えて

38

。特に

:あなたのラムダ式をasync voidとして扱われるよう

1)async() => await userController.Get("foo")は、voidを返しTestDelegate、に変換されます。テストランナーはラムダの実行を開始しますが、完了するのを待つことはありません。ラムダはGetが完了する前に戻ります(それはasyncなので)、テストランナーは例外なく返されたとみなします。

2)Waitは、AggregateExceptionにすべての例外をラップします。

3)また、asyncラムダはasync voidと扱われているため、テストランナーはその完了を待っていません。

4)async voidではなくasync Taskにすることをお勧めしますが、この場合、テストランナーは完了を待って例外を認識します。

this bug reportによれば、次のNUnitのビルドにはこの問題が修正されています。その間に、独自のThrowsAsyncメソッドを構築することができます。 example for xUnit is here

+0

ありがとうございます - 私はバグを疑いました、それが確認されてうれしいです。私は 'ThrowsAsync'アプローチを使用しますが、私が現在持っているものよりもはるかにクリーンです。 –

+0

あなたのアプローチはうまくいきます。アサート機能を追加するためにThrowsAsyncのアイデアを拡張する必要がありましたが、あまり難しくありませんでした。私は今私が持っているもので私の答えを更新しました。再度、感謝します。 –

+1

バグは2.6.3 – DalSoft

2

タスクを待っている場合、スローされた例外はAggregateExceptionに集約されます。 AggregateExceptionの内部例外を検査できます。これは、ケース2が機能しない理由です。

タスク内で実行されているユーザーコードによってスローされる未処理の例外は、このトピックの後半で説明する特定のシナリオを除いて、結合スレッドに伝播されます。例外は、静的またはインスタンスのTask.WaitまたはTask.Waitメソッドのいずれかを使用するときに伝播され、try-catchステートメントで呼び出しを囲むことによってそれらを処理します。タスクが添付された子タスクの親である場合、または複数のタスクを待機している場合、複数の例外がスローされる可能性があります。すべての例外を呼び出し元のスレッドに戻すために、タスクのインフラストラクチャはAggregateExceptionインスタンスにそれらをラップします。 AggregateExceptionには、Throwされたすべての元の例外を調べ、それぞれを個別に処理(または処理しない)するために列挙できるInnerExceptionsプロパティがあります。例外が1つだけスローされたとしても、それはAggregateExceptionでラップされます。

Link to MSDN

+0

はい、そうです。 'HttpResponseException'がスローされているかどうかを確認するために' AggregateException'を調べるのではなく、オプションがないように見えますか? –

+0

AggregateExceptionを調べる方法はないと思いますが、あまりにも悪くないと思います。 – roqz

11

This blog鉱山と同様の問題について会談。

私は勧告が提案に続いて、このようなテストを持っている -

[Test] 
    public void ShouldThrow404WhenNotFound() 
    { 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
     var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

     var aggregateException = Assert.Throws<AggregateException>(() => userController.Get("foo").Wait()); 
     var httpResponseException = aggregateException.InnerExceptions 
      .FirstOrDefault(x => x.GetType() == typeof(HttpResponseException)) as HttpResponseException; 

     Assert.That(httpResponseException, Is.Not.Null); 
     Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
    } 

私はそれであまりにも満足していないですが、これは動作します。

@StephenClearyに触発さ

EDIT 1は、私が探していますと主張し、静的なヘルパークラスを追加しました。それはこのようになります -

public static class AssertEx 
{ 
    public static async Task ThrowsAsync<TException>(Func<Task> func) where TException : class 
    { 
     await ThrowsAsync<TException>(func, exception => { }); 
    } 

    public static async Task ThrowsAsync<TException>(Func<Task> func, Action<TException> action) where TException : class 
    { 
     var exception = default(TException); 
     var expected = typeof(TException); 
     Type actual = null; 
     try 
     { 
      await func(); 
     } 
     catch (Exception e) 
     { 
      exception = e as TException; 
      actual = e.GetType(); 
     } 

     Assert.AreEqual(expected, actual); 
     action(exception); 
    } 
} 

のように、私は今、テストを持つことができます - あなたはasync voidに起因する問題を見ている

[Test] 
    public async void ShouldThrow404WhenNotFound() 
    { 
     var mockUserRepository = new Mock<IUserRepository>(); 
     mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
     var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

     Action<HttpResponseException> asserts = exception => Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
     await AssertEx.ThrowsAsync(() => userController.Get("foo"), asserts); 
    } 
19

私はそれが追加されたときにわからないんだけど、(執筆時点で3.4.1)のNUnitの現在のバージョンはThrowsAsyncの方法を含む

私はこの例をテストしていないhttps://github.com/nunit/docs/wiki/Assert.ThrowsAsync

を見ます具体的には、それは次のように動作するはずです:

[Test] 
public async void ShouldThrow404WhenNotFound() 
{ 
    var mockUserRepository = new Mock<IUserRepository>(); 
    mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User)); 
    var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() }; 

    var exception = Assert.ThrowsAsync<HttpResponseException>(() => userController.Get("foo")); 

    Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); 
} 
+4

から修正されました。この回答は、カスタマイズされたすべてのソリューションを試してみるのに時間を無駄にしないように、最上位にある必要があります。答えは既に即座にNUnitに組み込まれています –

+0

Phew、私はスクロールダウンを止めなかったことをうれしく思っています:) –

+0

Assert.ThrowsAsync <>を使用すると、あなたのテストは非同期(この場合)である必要はありません。ちょうどそれを無効にしてください。 – nashwan

関連する問題