2015-07-02 14 views
8

Próbuję napisać test dla metody Web API, która używa HttpContext.Current.Request.Files, a po wyczerpujących poszukiwaniach i eksperymentach nie mogę wymyślić, jak się z niej wyśmiewać. Sposób testowany wygląda następująco:Testowanie metody Web API korzystającej z HttpContext.Current.Request.Files?

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

Zdaję sobie sprawę, że istnieją other questionssimilar to this, ale one nie dotyczą tej konkretnej sytuacji.

Jeśli próbuję kpić z kontekstu, napotkam problemy z hierarchią obiektów Http*. Powiedzieć skonfigurować różne obiekty mock (używając Moq) tak:

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

próbując przypisać go do bieżącego kontekstu ...

HttpContext.Current = mockContext.Object; 

... powoduje błąd kompilatora/redline ponieważ to Cannot convert source type 'System.Web.HttpContextBase' to target type 'System.Web.HttpContext'.

Próbowałem również wiercenia w różnych obiektach kontekstu, które pochodzą z konstruowanym obiektem kontrolera, ale nie można znaleźć jeden, który a) jest obiektem powrotu połączenia HttpContext.Current w korpusie metody kontrolera i b) zapewnia dostęp do standardowego HttpRequest właściwości, takie jak Files.

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

Ważne jest również, aby pamiętać, że nie mogę zmienić kontroler że jestem testowania w ogóle, więc nie mogę zmienić, aby umożliwić konstruktora kontekst być wstrzykiwany.

Czy jest jakiś sposób na kpiny z HttpContext.Current.Request.Files dla testów jednostkowych w Web API?

Aktualizacja
Chociaż nie jestem pewien, że to zostanie przyjęte przez zespół, jestem eksperymentować ze zmianą metody POST używać Request.Content, jak sugeruje Martin Liversage. Obecnie wygląda mniej więcej tak:

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

Mój test wygląda podobnie do tego:

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

Teraz Dostaję błąd na 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

Odkładając na bok, jeśli naprawdę nie możesz zmienić kodu, aby usunąć bezpośrednie sprzężenie w zależności od 'HttpContext.Current', istnieje długi sposób na zrobienie tego [przez odbicie] (http: // stackoverflow. com/a/31177399/314291) – StuartLC

Odpowiedz

18

Web API został zbudowany w celu wspierania testów jednostkowych, pozwalając na symulowanie różnych obiektów kontekstowych. Jednak przy użyciu HttpContext.Current używasz "starego stylu" kodu System.Web, który używa klasy HttpContext, co uniemożliwia jednostkowe sprawdzenie kodu.

Aby umożliwić testowanie kodu, należy przerwać korzystanie z HttpContext.Current. W dokumencie Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME możesz zobaczyć, jak przesyłać pliki za pomocą interfejsu Web API. Jak na ironię, ten kod również używa HttpContext.Current, aby uzyskać dostęp do MapPath, ale w Web API powinieneś użyć HostingEnvironment.MapPath, który działa również poza IIS. Kpiny z nich są również problematyczne, ale na razie skupiam się na twoim pytaniu o wyśmiewanie się z prośbą.

Nie używając HttpContext.Current pozwala jednostce przetestować kontroler przypisując właściwości kontrolera 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; 
+0

Dziękuję za odpowiedź. Zobacz moją aktualizację powyżej dotyczącą dyspozycji treści. –

+0

@AJ: Zaktualizowałem kod. Musisz ustawić nagłówek "Content-Disposition" wewnętrznej treści, a nie na wieloczęściowej zawartości. –

+0

@MartinLiversage - Staram się, aby to działało, i nie mając dużo szczęścia - jeśli masz czas, masz szansę na to, proszę? http://stackoverflow.com/questions/44073646/httprequestmessage-content-disposition-null-when-unit-testing – Darren

2

Przyjęte rozwiązanie jest idealne dla pytaniu OP. Chciałem dodać tutaj moje rozwiązanie, które pochodzi od Martina, ponieważ jest to strona, do której zostałem skierowany, kiedy po prostu szukam, jak wyśmiewać obiekt Request dla Web API, aby móc dodać nagłówki, których szuka mój kontroler. Trudno było mi znaleźć prostą odpowiedź:

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

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

I oto jesteś; bardzo prosty sposób na stworzenie kontekstu kontrolera, w którym można "wyśmiać" obiekt żądania i dostarczyć poprawne nagłówki dla metody kontrolera.

Powiązane problemy