web-dev-qa-db-ja.com

HttpContext.Current.Request.Filesを使用するWeb APIメソッドをテストしますか?

HttpContext.Current.Request.Filesを使用するWeb APIメソッドのテストを記述しようとしていますが、徹底的な検索と実験を行った後、モックする方法がわかりません。テストされるメソッドは次のようになります。

[HttpPost]
public HttpResponseMessage Post()
{
    var requestFiles = HttpContext.Current.Request.Files;
    var file = requestFiles.Get(0);
    //do some other stuff...
}

その他の質問これと同様 があることを私は理解していますが、それらはこの特定の状況に対処していません。

コンテキストをモックしようとすると、Http*オブジェクト階層で問題が発生します。次のようにして、さまざまなモックオブジェクトを設定します( Moq を使用)。

var mockFiles = new Mock<HttpFileCollectionBase>();
mockFiles.Setup(s => s.Count).Returns(1);
var mockFile = new Mock<HttpPostedFileBase>();
mockFile.Setup(s => s.InputStream).Returns(new MemoryStream());
mockFiles.Setup(s => s.Get(It.IsAny<int>())).Returns(mockFile.Object);
var mockRequest = new Mock<HttpRequestBase>();
mockRequest.Setup(s => s.Files).Returns(mockFiles.Object);
var mockContext = new Mock<HttpContextBase>();
mockContext.Setup(s => s.Request).Returns(mockRequest.Object);

現在のコンテキストに割り当てようとしています...

HttpContext.Current = mockContext.Object;

...Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'であるため、コンパイラエラー/レッドラインが発生します。

また、構築されたコントローラーオブジェクトに付属するさまざまなコンテキストオブジェクトをドリルダウンしようとしましたが、a)コントローラーメソッド本体のHttpContext.Current呼び出しの戻りオブジェクトであり、b)標準へのアクセスを提供するものを見つけることができませんHttpRequestプロパティ(Filesなど)。

var requestMsg = controller.Request;   //returns HttpRequestMessage
var context = controller.ControllerContext;  //returns HttpControllerContext
var requestContext = controller.RequestContext;   //read-only returns HttpRequestContext

テストしているコントローラーをまったく変更できないため、コンテキストを挿入できるようにコンストラクターを変更できないことに注意することも重要です

Web APIでの単体テストのためにHttpContext.Current.Request.Filesをモックする方法はありますか?

更新
これがチームに受け入れられるかどうかはわかりませんが、 Martin Liversage で提案されているように、Postメソッドを変更してRequest.Contentを使用するように実験しています。現在は次のようになっています。

public async Task<HttpResponseMessage> Post()
{
    var uploadFileStream = new MultipartFormDataStreamProvider(@"C:\temp");
    await Request.Content.ReadAsMultipartAsync(uploadFileStream);
    //do the stuff to get the file
    return ActionContext.Request.CreateResponse(HttpStatusCode.OK, "it worked!");
}

私のテストはこれに似ています:

var byteContent = new byte[]{};
var content = new MultipartContent { new ByteArrayContent(byteContent) };
content.Headers.Add("Content-Disposition", "form-data");
var controllerContext = new HttpControllerContext 
{
    Request = new HttpRequestMessage
        {
            Content = new MultipartContent { new ByteArrayContent(byteContent) }
        }
};

現在、ReadAsMultipartAsyncでエラーが発生しています。

System.IO.IOException: Error writing MIME multipart body part to output stream. ---> System.InvalidOperationException: The stream provider of type 'MultipartFormDataStreamProvider' threw an exception. ---> System.InvalidOperationException: Did not find required 'Content-Disposition' header field in MIME multipart body part.

10
AJ.

Web APIは、さまざまなコンテキストオブジェクトをモックできるようにすることで、ユニットテストをサポートするように構築されています。ただし、HttpContext.Currentを使用すると、HttpContextクラスを使用する「古いスタイル」のSystem.Webコードが使用されるため、コードを単体テストすることができなくなります。

コードを単体テスト可能にするには、HttpContext.Currentの使用を停止する必要があります。 ASP.NET Web APIでのHTMLフォームデータの送信:ファイルのアップロードとマルチパートMIME では、Web APIを使用してファイルをアップロードする方法を確認できます。皮肉なことに、このコードでもHttpContext.Currentを使用してMapPathにアクセスしていますが、Web APIではIISの外部でも機能するHostingEnvironment.MapPathを使用する必要があります。後者をモックすることも問題ですが、今のところ、リクエストのモックについてのあなたの質問に焦点を当てています。

HttpContext.Currentを使用しない場合は、コントローラーのControllerContextプロパティを割り当てることで、コントローラーを単体テストできます。

var content = new ByteArrayContent( /* bytes in the file */ );
content.Headers.Add("Content-Disposition", "form-data");
var controllerContext = new HttpControllerContext {
  Request = new HttpRequestMessage {
    Content = new MultipartContent { content }
  }
};
var controller = new MyController();
controller.ControllerContext = controllerContext;
27

受け入れられた回答は、OPの質問に最適です。 Martinから派生したソリューションをここに追加したいと思います。これは、Web APIのRequestオブジェクトをモックアウトして、コントローラーが探しているヘッダーを追加できるようにする方法を単に検索するときに向けられたページだからです。私は簡単な答えを見つけるのに苦労しました:

   var controllerContext = new HttpControllerContext();
   controllerContext.Request = new HttpRequestMessage();
   controllerContext.Request.Headers.Add("Accept", "application/xml");

   MyController controller = new MyController(MockRepository);
   controller.ControllerContext = controllerContext;

そして、あなたはそこにいます。 Requestオブジェクトを「モック」してControllerメソッドの正しいヘッダーを提供できるコントローラーコンテキストを作成する非常に簡単な方法。

2
iGanja

投稿されたファイルだけをあざけりました。私はすべてのファイルもこの方法で偽造できると信じています。

これは私のコントローラーにありました

private HttpPostedFileBase _postedFile;

/// <summary>
/// For mocking HttpPostedFile
/// </summary>
public HttpPostedFileBase PostedFile
{
    get
    {
        if (_postedFile != null) return _postedFile;
        if (HttpContext.Current == null)
        {
            throw new InvalidOperationException("HttpContext not available");
        }
        return new HttpContextWrapper(HttpContext.Current).Request.Files[0];
    }
    set { _postedFile = value; }
}

[HttpPost]
public MyResponse Upload()
{
    if (!ValidateInput(this.PostedFile))
    {
        return new MyResponse
        {
            Status = "Input validation failed"
        };
    }
}

private bool ValidateInput(HttpPostedFileBase file)
{
    if (file.ContentLength == 0)
        return false;

    if (file.ContentType != "test")
        return false;

    if (file.ContentLength > (1024 * 2048))
        return false;

    return true
}

これは私のユニットテストケースにありました

public void Test1()
{
    var controller = new Mock<MyContoller>();
    controller.Setup(x => x.Upload).Returns(new CustomResponse());

    controller.Request = new HttpRequestMessage();
    controller.Request.Content = new StreamContent(GetContent());
    controller.PostedFile = GetPostedFile();

    var result = controller.Upload().Result;
}

private HttpPostedFileBase GetPostedFile()
{
    var postedFile = new Mock<HttpPostedFileBase>();
    using (var stream = new MemoryStream())
    using (var bmp = new Bitmap(1, 1))
    {
        var graphics = Graphics.FromImage(bmp);
        graphics.FillRectangle(Brushes.Black, 0, 0, 1, 1);
        bmp.Save(stream, ImageFormat.Jpeg);
        postedFile.Setup(pf => pf.InputStream).Returns(stream);
        postedFile.Setup(pf => pf.ContentLength).Returns(1024);
        postedFile.Setup(pf => pf.ContentType).Returns("bmp");
    }
    return postedFile.Object;
}

ただし、HTTPContextを正常にモックすることはできませんでした。しかし、私はファイルのアップロードをあざけることができました。

0
Abhishek