wake-up-neo.net

Testen einer Web-API-Methode, die HttpContext.Current.Request.Files verwendet?

Ich versuche, einen Test für eine Web-API-Methode zu schreiben, die HttpContext.Current.Request.Files verwendet, und nach eingehender Suche und Experimentieren kann ich nicht herausfinden, wie ich mich dafür lustig machen soll. Die getestete Methode sieht folgendermaßen aus:

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

Mir ist klar, dass es andere Fragenähnliche gibt , aber sie gehen nicht auf diese spezielle Situation ein.

Wenn ich versuche, den Kontext zu verspotten, treten Probleme mit der Http* -Objekthierarchie auf. Angenommen, ich habe verschiedene Mock-Objekte (mit Moq ) wie folgt eingerichtet:

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);

Es wird versucht, es dem aktuellen Kontext zuzuweisen ...

HttpContext.Current = mockContext.Object;

... führt zu einem Compiler-Fehler/einer Redline, weil es Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext' ist.

Ich habe auch versucht, in verschiedene Kontextobjekte zu bohren, die mit dem erstellten Controller-Objekt geliefert werden, kann jedoch kein Objekt finden, das a) das Rückgabeobjekt eines HttpContext.Current-Aufrufs im Controller-Methodenkörper ist und b) Zugriff auf standardmäßige HttpRequest-Eigenschaften bietet, z Files.

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

Es ist auch wichtig zu beachten, dass Ich kann den zu testenden Controller überhaupt nicht ändern, daher kann ich den Konstruktor nicht ändern, um das Einfügen des Kontexts zu ermöglichen.

Gibt es eine Möglichkeit, HttpContext.Current.Request.Files für Komponententests in der Web-API zu verspotten?

Update
Obwohl ich nicht sicher bin, ob dies vom Team akzeptiert wird, experimentiere ich mit der Änderung der Post-Methode zur Verwendung von Request.Content, wie von Martin Liversage vorgeschlagen. Derzeit sieht es ungefähr so ​​aus:

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!");
}

Mein Test sieht ungefähr so ​​aus:

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) }
        }
};

Jetzt erhalte ich einen Fehler bei 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.

8
AJ.

Die Web-API wurde zur Unterstützung von Komponententests entwickelt, indem Sie verschiedene Kontextobjekte nachahmen können. Wenn Sie jedoch HttpContext.Current verwenden, verwenden Sie den System.Web-Code "old style", der die HttpContext-Klasse verwendet, die es unmöglich macht, einen Komponententest durchzuführen.

Damit Ihr Code Unit-fähig ist, müssen Sie HttpContext.Current nicht mehr verwenden. Unter Senden von HTML-Formulardaten in ASP.NET-Web-API: Datei-Upload und Multipart-MIME Sie können sehen, wie Dateien mit der Web-API hochgeladen werden. Ironischerweise verwendet dieser Code auch HttpContext.Current, um Zugriff auf die MapPath zu erhalten. In der Web-API sollten Sie jedoch HostingEnvironment.MapPath verwenden, der auch außerhalb von IIS funktioniert. Später zu verspotten ist auch problematisch, aber im Moment konzentriere ich mich auf Ihre Frage, wie Sie die Anfrage verspotten.

Wenn Sie HttpContext.Current nicht verwenden, können Sie Ihren Controller durch Testen der ControllerContext-Eigenschaft des Controllers testen:

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;
21

Die akzeptierte Antwort ist perfekt für die Frage des OP. Ich wollte hier meine Lösung hinzufügen, die von Martin stammt, da dies die Seite ist, auf die ich verwiesen wurde, als ich einfach suchte, wie ich das Request-Objekt für Web-API ausspreche, damit ich Header hinzufügen kann, nach denen mein Controller sucht. Es fiel mir schwer, die einfache Antwort zu finden:

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

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

Und da bist du; Eine sehr einfache Möglichkeit, einen Controller-Kontext zu erstellen, mit dem Sie das Request-Objekt "simulieren" und die richtigen Header für Ihre Controller-Methode angeben können.

2
iGanja

Ich habe nur die gepostete Datei verspottet. Ich glaube, alle Dateien können auch auf diese Weise verspottet werden.

Das war in meinem Controller

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
}

Dies war in meinem Unit-Testfall

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;
}

Obwohl ich den HTTPContext nicht erfolgreich verspotten konnte. Aber ich Konnte den Upload der Datei nachahmen.

0
Abhishek