リクエストコンテンツからのインスタンス化とアクション呼び出しでMVCコントローラーをテストするにはどうすればよいですか?

BartoszKP

Javascriptでバックボーンモデルをテスト駆動したので、ユーザーが[保存]ボタンをクリックすると、適切なPOST要求がASP.NETMVCアプリケーションに送信されていると確信しています。これは、最終的な統合のようなこのモデルのテスト(あるthis.serverあるSinon.JSから偽のサーバ):

it('should properly formulate request to save data', function () {
    this.model.data = [{ id: 1, type: 'type', value: 'value' }];

    this.model.save();

    expect(this.server.requests.length).toEqual(1);
    expect(this.server.requests[0].method).toEqual('POST');
    expect(this.server.requests[0].url).toEqual('MyController/SaveData');
    expect(this.server.requests[0].requestHeaders['Content-Type'])
        .toEqual('application/json;charset=utf-8');
    expect(this.server.requests[0].requestBody)
        .toEqual('[{"id":1,"type":"type","value":"value"}]');
});

今度はコントローラーを試乗したいと思います。SaveDataアクションが適切に実装されていることを確認するだけでなく(簡単です)、リクエストの本文からアクション引数およびMVCルートへのマッピングが適切であることを確認したいと思います。

HttpContextBaseたとえば、スタブ付きのユニットテストコントローラーに関する多くの質問を見つけました

残念ながら、それらはすべてコントローラーをインスタンス化し、アクションメソッドを手動で呼び出します。これは私には不十分です。JSから送信される(そして前述の単体テストによって保護されている)同じ要求コンテンツが、ASP.NETアプリケーション側で正しく機能することを主張したいと思います。

私が現在持っているのは、それを機能させて問題を説明するためのドラフトです。スタブとモックにはRhinoモック使用しています。特に、dataWebServiceアサーションに使用したいモックです。テストのポイントを明確にするためにそれを含めましたが、一般的にはもちろん問題とは無関係です。問題は2つあり(コントローラーのインスタンス化とアクションの呼び出し)、次のコードのコメントで示されます。

[Test]
public void GivenNoData_WhenPostingData_ThenCallsWebServiceSaveData()
{
    var httpContext = MockRepository.GenerateStub<HttpContextBase>();
    var httpRequest = MockRepository.GenerateStub<HttpRequestBase>();

    httpRequest
        .Stub(hr => hr.Url)
        .Return(new Uri("http://localhost/MyController/SaveData"));
    httpRequest
        .Stub(hr => hr.Headers)
        .Return(new WebHeaderCollection()
        {
            { "X-Requested-With", "XMLHttpRequest" },
            { "Content-Type", "application/json;charset=utf-8" }
        });
    httpRequest
        .Stub(hr => hr.RequestType)
        .Return("POST");

    var requestBody = @"[{""id"":1,""type"":""type"",""value"":""value""}]";
    httpRequest
        .Stub(hr => hr.InputStream)
        .Return(new MemoryStream(Encoding.UTF8.GetBytes(requestBody)));

    httpContext.Stub(hc => hc.Request).Return(httpRequest);

    // The problem starts here
    // I want MVC to instantiate the controller based on the request

    var controller = new MyController(dataWebService);
    controller.ControllerContext
        = new ControllerContext(httpContext, new System.Web.Routing.RouteData(), controller);

    dataWebService.Expect(dws => dws.SaveData(Arg<Data>.Matches(/*...*/));

    // Second part of the problem, I want MVC to invoke SaveData with arguments
    // generated from request's body
    controller.SaveData(/* arguments */);

    dataWebService.VerifyAllExpectations();    
}

さて、これは単体テストの厳密な定義と一致せず、単体テストと統合テストの間のどこかにあることを私は知っています。

ただし、最初にプロセス全体が上から下までテストでカバーされていることを確認したいので、次に定義について心配します(そして、おそらくテストをコントローラーのみの単体テストと統合のようなものに分割します)ルーティングとコントローラー引数の解析をテストします)。

また、MVCが正しく機能すると仮定すると、重要なのは自分のコード、特にSaveDataメソッドシグネチャとMVCルート構成のみをテストすることです。

jtabuloc

これは私には統合テストのように聞こえます。とにかく、この場合はRhinoMockを忘れてください。これを行うには、ここで独自のテストスイートを作成する以外に方法はありません。この例では、実際にHttpClientを使用してコントローラー/ APIを呼び出し、アクションに必要なURLと引数を渡して、検証のために結果を予測しました。

public class ClientHandler
{
   private HttpClient _client;

   public ClientHander()
   {
        // add handler if needed ex var handler = new HttpClientHandler()

        _client = new HttpClient(/*handler*/);

        // add default header if needed client.DefaultRequestHeaders
   }

   public HttpResponseMessage Post(string path, HttpContent content)
   {
      // You can async if you want
      return _client.Post(path, content).Result;
   }
}

これで、実際のテストで使用できます。

[TestClass]
public class MyController
{
    [TestMethod]
    public void TestMyControllerActionSaveData()
    {  
         var client = new ClientHandler();
         var content = new StringContent(dataHere, Encoding.UTF8, "application/json");
         var outPut = client.Post("http://server/action/here", content).Result;

         // validate expected output here
    }
}

ここには不足している構成とセットアップがたくさんありますが、要点はそこにあります。

更新:これは継続的インテグレーション(CI)の一部としての強力なツールであるため、テストという名目で現在行っていることを本当に気に入っています。テストメソッドに名前を付ける方法について、ちょっとしたコメントをしてください。私はあなたが何のように、テスト内のこれらの手順を実行して入れたい行動にテストメソッドの名前を変更することをお勧めBDDのガーキン方法やによって記載されているようにダン北

[TestMethod]
public void Should_Save_If_Has_Data()
{
   Given(Web_Service_Instance)
     With(Data_To_Pass)
     When(Posting_Data_To_Service)
     Then(Data_Will_Be_Saved)
     Verify()
}

[TestMethod]
public void Should_Not_Save_If_No_Data()
{
    .....
}

上記のようなTestSuiteを作成できれば、長期的には多くのメリットが得られ、コードの繰り返し(DRY)を回避できます。また、これらのテストは、Robert C. Martin&MicahMartinによって説明されているように生きたドキュメントとして機能します。イムが関わったチームに特に感謝し、称賛は彼らのためです!

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ