2015-07-02 3 views
8

Ich versuche, einen Test für eine Web-API-Methode zu schreiben, die HttpContext.Current.Request.Files verwendet, und nach erschöpfenden Suchen und Experimentieren kann ich nicht herausfinden, wie man dafür spottet. Das Verfahren getestet wird, sieht wie folgt aus:Testen einer Web-API-Methode, die HttpContext.Current.Request.Files verwendet?

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

ich erkennen, dass es other questionssimilar to this, aber sie haben nicht diese spezifische Situation befassen.

Wenn ich versuche, den Kontext zu verspotten, stoße ich auf Probleme mit der Objekthierarchie . Sagen wir, ich verschiedene Mock-Objekte (mit Moq) wie folgt ein:

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

Der Versuch, es zu den aktuellen Kontext zuordnen ...

HttpContext.Current = mockContext.Object; 

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

Ich habe auch versucht, Bohren in verschiedene Kontextobjekte, die mit dem konstruierten Controller-Objekt kommen, aber nicht finden, dass a) ist das Rückgabeobjekt eines HttpContext.Current Aufruf in der Controller-Methode body und b) bietet Zugriff auf Standard HttpRequest Eigenschaften, wie 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 Controller ändern, die ich überhaupt testen, so kann ich nicht den Konstruktor ändert der Kontext zu ermöglichen, injiziert werden.

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

aktualisiert
Obwohl ich nicht sicher bin, wird vom Team akzeptiert werden, experimentiere ich die Post-Methode mit wechselnden Request.Content zu verwenden, wie von Martin Liversage vorgeschlagen. Es sieht derzeit so etwas wie dieses:

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 in etwa so aussieht:

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

nun einen Fehler auf ReadAsMultipartAsync Ich erhalte:

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.

+1

Wenn Sie nicht wirklich Ihren Code ändern können, um die direkte Kopplung für die 'HttpContext.Current'-Abhängigkeit zu entfernen, gibt es einen langen Weg [dies über Reflektion] (http: // stackoverflow. com/a/31177399/314291) – StuartLC

Antwort

18

Web API hat wurde entwickelt, um den Komponententest zu unterstützen, indem es Ihnen erlaubt, verschiedene Kontextobjekte nachzuahmen. Bei Verwendung von HttpContext.Current verwenden Sie jedoch den "alten" System.Web-Code, der die HttpContext-Klasse verwendet, die es unmöglich macht, Ihren Code zu testen.

Damit Ihr Code testbar ist, müssen Sie aufhören, HttpContext.Current zu verwenden. In Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME können Sie sehen, wie Sie Dateien mit Web-API hochladen. Ironischerweise verwendet dieser Code auch HttpContext.Current, um auf die MapPath zuzugreifen, aber in der Web-API sollten Sie HostingEnvironment.MapPath verwenden, das auch außerhalb von IIS funktioniert. Das verspotten ist auch problematisch, aber im Moment konzentriere ich mich auf Ihre Frage, wie Sie die Anfrage verspotten.

Nicht HttpContext.Current Verwendung können Sie Einheit des Controllers testen, indem Sie die ControllerContext Eigenschaft des Controllers zuweisen:

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; 
+0

Vielen Dank für Ihre Antwort. Bitte beachten Sie mein Update oben zur Content-Disposition. –

+0

@AJ: Ich habe den Code aktualisiert. Sie müssen den Header "Content-Disposition" des inneren Inhalts festlegen, nicht den mehrteiligen Inhalt. –

+0

@MartinLiversage - Ich versuche, dies zur Arbeit zu bringen und nicht viel Glück zu haben - wenn Sie Zeit haben, können Sie eine Chance haben, sich das bitte anzusehen? http://stackoverflow.com/questions/44073646/httprequestmessage-content-disposition-null-when-unit-testing – Darren

2

Die akzeptierte Antwort für die OP Frage perfekt. Ich wollte hier meine Lösung hinzufügen, die von Martins abgeleitet ist, da dies die Seite ist, auf die ich verwiesen wurde, als ich einfach nach dem Request-Objekt für die Web-API suchte, damit ich Header hinzufügen kann, nach denen mein Controller sucht. Ich hatte eine schwierige Zeit, 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 dort sind Sie; Eine sehr einfache Möglichkeit, einen Controller-Kontext zu erstellen, mit dem Sie das Request-Objekt "ausblenden" und die richtigen Header für Ihre Controller-Methode angeben können.

Verwandte Themen