2015-07-02 18 views
8

Sto tentando di scrivere un test per un metodo API Web che utilizza HttpContext.Current.Request.Files e dopo una ricerca esauriente e una sperimentazione non riesco a capire come deriderlo. Il metodo in fase di sperimentazione è simile al seguente:Test di un metodo API Web che utilizza HttpContext.Current.Request.Files?

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

mi rendo conto che ci sono other questionssimilar to this, ma non affrontano questa situazione specifica.

Se tento di simulare il contesto, mi imbatto in problemi con la gerarchia di oggetti Http*. Dì che impostare vari oggetti fittizi (utilizzando Moq) come questo:

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

tentativo di assegnare al contesto attuale ...

HttpContext.Current = mockContext.Object; 

... produce un errore di compilatore/redline perché it Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'.

Ho anche provato a eseguire il drill in vari oggetti di contesto forniti con l'oggetto controller costruito, ma non riesco a trovarne uno a) è l'oggetto di ritorno di una chiamata HttpContext.Current nel corpo del metodo controller eb) fornisce accesso allo standard HttpRequest proprietà, come Files.

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

E 'anche importante notare che non posso cambiare il controller che sto testando affatto, quindi non può cambiare il costruttore per consentire il contesto da iniettare.

Esiste un modo per simulare lo HttpContext.Current.Request.Files per il test dell'unità nell'API Web?

Aggiornamento
Anche se non sono sicuro che questo sarà accettata dal team, sto sperimentando con cambiando il metodo POST per usare Request.Content, come suggerito da Martin Liversage. E 'attualmente simile a questa:

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

Il mio test è simile a questo:

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

Ora sto ottenendo un errore sul 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.

+1

Per inciso, se davvero non è possibile modificare il codice per rimuovere l'accoppiamento diretto sulla dipendenza 'HttpContext.Current', c'è un modo prolisso di fare [questo tramite riflessione] (http: // StackOverflow. it/a/31177399/314291) – StuartLC

risposta

18

Web API ha è stato creato per supportare il test delle unità consentendo di prendere in giro vari oggetti di contesto. Tuttavia, utilizzando HttpContext.Current si utilizza il codice "vecchio stile" System.Web che utilizza la classe HttpContext che rende impossibile testare il codice dell'unità.

Per consentire il test del codice dell'unità, è necessario interrompere l'uso di HttpContext.Current. In Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME puoi vedere come caricare i file utilizzando l'API Web. Ironia della sorte, questo codice utilizza anche HttpContext.Current per accedere a MapPath ma in Web API è necessario utilizzare HostingEnvironment.MapPath che funzioni anche all'esterno di IIS. Scherzare il dopo è anche problematico, ma per ora mi sto concentrando sulla tua domanda su come deridere la richiesta.

Non usando HttpContext.Current consente all'unità verificare la periferica assegnando la proprietà ControllerContext del controller:

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

Grazie per la risposta. Si prega di consultare il mio aggiornamento sopra a proposito di disposizione-disposizione. –

+0

@AJ: Ho aggiornato il codice. Devi impostare l'intestazione 'Content-Disposition' del contenuto interno, non sul contenuto multiparte. –

+0

@MartinLiversage - Sto cercando di far funzionare tutto questo, e non ho molta fortuna - se hai tempo, hai qualche possibilità di dare un'occhiata a questo per favore? http://stackoverflow.com/questions/44073646/httprequestmessage-content-disposition-null-when-unit-testing – Darren

2

La risposta accettata è perfetto per la domanda del PO. Volevo aggiungere qui la mia soluzione, che deriva da Martin, in quanto questa è la pagina a cui mi sono indirizzato quando cercavo semplicemente come eseguire il Mockout dell'oggetto Request per le API Web in modo da poter aggiungere le intestazioni che il mio controller sta cercando. Ho avuto difficoltà a trovare la risposta semplice:

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

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

E ci sei; un modo molto semplice per creare il contesto del controller con cui è possibile "Mock" l'oggetto Request e fornire le intestazioni corrette per il metodo Controller.